MuJoCo 入门教程(一)

系列文章目录


前言


一、简介

        MuJoCo 是多关节接触动力学(Multi-Joint dynamics with Contact)的缩写。它是一个通用物理引擎,旨在促进机器人、生物力学、图形和动画、机器学习以及其他需要快速、准确地仿真铰接结构与环境交互的领域的研究和开发。该引擎最初由 Roboti LLC 开发,2021 年 10 月被 DeepMind 收购并免费提供,2022 年 5 月开源。MuJoCo 代码库位于 GitHub 上的 deepmind/mujoco 代码库中。

        MuJoCo 是一个带有 C API 的 C/C++ 库,面向研究人员和开发人员。运行时仿真模块经过调整,以最大限度地提高性能,并在低级数据结构上运行,这些数据结构由内置的 XML 解析器和编译器预先分配。用户可使用本地 MJCF 场景描述语言定义模型,该语言是一种 XML 文件格式,旨在尽可能方便用户阅读和编辑。也可以加载 URDF 模型文件。该库包括一个以 OpenGL 渲染的本地 GUI 交互式可视化。MuJoCo 还提供了大量用于计算物理相关量的实用功能。

        MuJoCo 可用于实现基于模型的计算,如控制合成、状态估计、系统识别、机构设计、通过逆动力学进行数据分析,以及用于机器学习应用的并行采样。它还可以用作更传统的仿真器,包括用于游戏和交互式虚拟环境。

1.1 主要功能

        MuJoCo 拥有众多功能。在此,我们将概述其中最显著的特点。

1.1.1 广义坐标与现代接触动力学相结合

        物理引擎传统上分为两类。机器人和生物力学引擎在广义坐标或关节坐标中使用高效、精确的递归算法。不过,它们要么不使用接触动力学,要么依赖于需要很小时间步长的早期弹簧阻尼方法。游戏引擎则采用更现代的方法,通过求解优化问题找到接触力。然而,它们通常采用过度指定的笛卡尔表示法,在这种表示法中,关节约束条件是以数值方式施加的,当涉及复杂的运动结构时,会导致不准确和不稳定。MuJoCo 是第一个将广义坐标仿真和基于优化的接触动力学结合起来的通用引擎。其他仿真器最近也进行了调整,以使用 MuJoCo 的方法,但这通常与它们的所有功能不兼容,因为它们从一开始就不是为这样做而设计的。习惯于游戏引擎的用户一开始可能会发现广义坐标有违直觉;请参阅下文的 "说明 "部分。

1.1.2 柔性、凸优化和解析可逆接触动力学

        在现代接触动力学方法中,摩擦接触所产生的力或冲力通常被定义为线性或非线性互补问题(linear complementarity problem,LCP 或 NCP)的解,而这两种问题都是 NP-hard 的。MuJoCo 基于对接触物理学的不同表述,可简化为一个凸优化问题,详见 "计算 "一章。我们的模型允许软接触和其他约束条件,并具有唯一定义的逆,便于数据分析和控制应用。有多种优化算法可供选择,包括可处理椭圆摩擦锥(elliptic friction cones)的投影高斯-赛德尔法(projected Gauss-Seidel method)的广义算法。求解器统一处理摩擦接触,包括扭转和滚动摩擦、无摩擦接触、关节和肌腱限制、关节和肌腱中的干摩擦,以及各种等式约束。

1.1.3 肌腱几何学

        MuJoCo 可以对肌腱的三维几何形状进行建模--肌腱是最小路径长度的弦,服从缠绕和通点约束。该机制与 OpenSim 中的机制类似,但实施了一套更受限制的封闭式缠绕选项,以加快计算速度。它还提供机器人专用结构,如滑轮和耦合自由度。肌腱可用于驱动,也可用于对肌腱长度施加不等式或等式约束。

1.1.4 通用执行模型

        在使用与模型无关的应用程序接口(API)的同时设计一个足够丰富的执行器模型是一项挑战。为了实现这一目标,MuJoCo 采用了一种抽象的执行器模型,该模型可具有不同类型的传动、力产生和内部动力学(即使整体动力学达到三阶的状态变量)。这些组件可以实例化,从而以统一的方式模拟电机、气动和液压缸、PD 控制器、生物肌肉和许多其他执行器。

1.1.5 可重新配置的计算管道

        MuJoCo 有一个顶级步进函数 mj_step,用于运行整个前向动力学并推进仿真状态。不过,在仿真之外的许多应用中,能够运行计算管道的选定部分是有益的。为此,MuJoCo 提供了大量可任意组合设置的标志,允许用户根据需要重新配置管道,而不仅仅是通过选项选择算法和算法参数。此外,许多底层函数可以直接调用。用户定义的回调可以实现自定义力场、执行器、碰撞例程和反馈控制器。

1.1.6 模型编译

        如上所述,用户以一种名为 MJCF 的 XML 文件格式定义一个 MuJoCo 模型。然后,内置编译器会将该模型编译为底层数据结构 mjModel,该结构具有交叉索引,并针对运行时计算进行了优化。编译后的模型也可以保存为二进制 MJB 文件。

1.1.7 模型与数据分离

        MuJoCo 在运行时将仿真参数分离成两个数据结构(C 结构):

  • mjModel 包含模型描述并保持不变。其中嵌入的其他结构包含仿真和可视化选项,这些选项需要偶尔更改,但这由用户完成。
  • mjData 包含所有动态变量和中间结果。所有函数都在这里读取输入并写入输出 —— 这些输出将成为仿真管道后续阶段的输入。它还包含一个预分配和内部管理的堆栈,因此运行时模块无需调用内存分配功能。

        mjData 在运行时根据 mjModel 生成。这种分离使得仿真多个模型以及每个模型的多个状态和控制变得容易,反过来又促进了采样和有限差分的多线程运行。顶层应用程序接口函数反映了这种基本分离,其格式为

void mj_step(const mjModel* m, mjData* d);

1.1.8 交互式仿真和可视化

        本地三维可视化器提供网格和几何基元、纹理、反射、阴影、雾、透明度、线框、天空盒、立体可视化(在支持四缓冲 OpenGL 的显卡上)的渲染。该功能用于生成三维渲染,帮助用户深入了解物理仿真,包括自动生成的模型骨架、等效惯性盒、接触位置和法线、可分为法线和切向分量的接触力、外部扰动力、局部坐标系、关节和致动器轴以及文本标签等可视化辅助工具。可视化器希望使用带有 OpenGL 渲染上下文的通用窗口,从而允许用户采用自己选择的图形用户界面库。随 MuJoCo 一起发布的代码仿真.cc 演示了如何使用 GLFW 库来实现这一点。一个相关的可用性功能是 "进入 "仿真,推动物体并查看物理响应的能力。用户选择要施加外力和扭矩的物体,就能看到扰动及其动态结果的实时渲染。这可用于对模型进行可视化调试,测试反馈控制器的响应,或将模型配置为所需的姿势。

1.1.9 强大而直观的建模语言

        MuJoCo 有自己的建模语言,称为 MJCF。MJCF 的目标是提供对 MuJoCo 所有计算功能的访问,同时使用户能够快速开发新模型并进行实验。这一目标的实现在很大程度上要归功于广泛的默认设置机制,该机制类似于 HTML 中内嵌的层叠样式表(CSS)。虽然 MJCF 有许多元素和属性,但在任何给定模型中,用户需要设置的元素和属性却少得惊人。这使得 MJCF 文件比许多其他格式更简短、更易读。

1.1.10 自动生成复合柔性对象

        MuJoCo 的柔性约束可用于对绳索、布料和可变形三维物体进行建模。这需要大量的规则体、关节、筋腱和约束一起工作。建模语言具有高级宏,可由模型编译器自动扩展为必要的标准模型元素集合。重要的是,这些生成的灵活对象能够与仿真的其他部分完全交互。

1.2 模型实例

        MuJoCo 中有几个实体称为 "模型"。用户在以 MJCF 或 URDF 编写的 XML 文件中定义模型。然后,软件可以在不同介质(文件或内存)和不同描述级别(高或低)上创建同一模型的多个实例。如下表所示,所有组合都是可能的:

High level

Low level

File

MJCF/URDF (XML)

MJB (binary)

Memory

mjCModel (C++ class)

mjModel (C struct)

        所有运行时计算都通过 mjModel 执行,而 mjModel 太复杂,无法手动创建。这就是我们有两级建模的原因。高层建模是为了方便用户:它的唯一目的是编译成低层模型,并在低层模型上进行计算。生成的 mjModel 可以加载并保存为二进制文件 (MJB),但这些文件是特定版本的,无法反编译,因此模型应始终保持为 XML 文件。

        (内部)C++ 类 mjCModel 与 MJCF 文件格式大致一一对应。XML 解析器可解释 MJCF 或 URDF 文件并创建相应的 mjCModel。原则上,用户可以通过编程创建 mjCModel,然后将其保存到 MJCF 或进行编译。然而,由于 C++ API 无法从独立于编译器的库中导出,因此这一功能尚未公开。我们计划围绕它开发一个 C 语言包装器,但目前解析器和编译器总是一起调用,而且模型只能以 XML 创建。

        下图显示了获取 mjModel 的不同路径(同样,第二点还不可用):

  • (文本编辑器)→MJCF/URDF 文件→(MuJoCo 解析器→mjCModel→MuJoCo 编译器)→mjModel
  • (用户代码)→ mjCModel→(MuJoCo 编译器)→ mjModel
  • MJB 文件→(MuJoCo 加载器)→ mjModel

1.3 示例

        下面是一个以 MuJoCo 的 MJCF 格式制作的简单模型。它定义了一个固定于世界的平面、一束用于更好地照亮物体和投射阴影的光线,以及一个具有 6 个自由度的浮动框(这就是 "自由 "关节的作用)。

<mujoco><worldbody><light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/><geom type="plane" size="1 1 0.1" rgba=".9 0 0 1"/><body pos="0 0 1"><joint type="free"/><geom type="box" size=".1 .2 .3" rgba="0 .9 0 1"/></body></worldbody>
</mujoco>

内置的 OpenGL 可视化器将该模型渲染为

如果对该模型进行仿真,箱子会落在地面上。下面给出了不进行渲染的被动动力学基本仿真代码。

#include "mujoco.h"
#include "stdio.h"char error[1000];
mjModel* m;
mjData* d;int main(void)
{// load model from file and check for errorsm = mj_loadXML("hello.xml", NULL, error, 1000);if( !m ){printf("%s\n", error);return 1;}// make data corresponding to modeld = mj_makeData(m);// run simulation for 10 secondswhile( d->time<10 )mj_step(m, d);// free model and datamj_deleteData(d);mj_deleteModel(m);return 0;
}

        从技术上讲,这是一个 C 文件,但它也是一个合法的 C++ 文件。事实上,MuJoCo 应用程序接口与 C 和 C++ 都兼容。通常情况下,用户代码会用 C++ 编写,因为这样既方便,又不会牺牲效率,因为计算瓶颈在仿真器中,而仿真器已经进行了高度优化。

        函数 mj_step 是将仿真状态推进一个时间步的顶层函数。当然,这个例子只是一个被动的动力系统。当用户指定控制或施加力并开始与系统交互时,情况就会变得更加有趣。

        接下来,我们将提供一个更详细的示例来说明 MJCF 的若干功能。请看下面的示例.xml:

<mujoco model="example"><default><geom rgba=".8 .6 .4 1"/></default><asset><texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/></asset><worldbody><light pos="0 1 1" dir="0 -1 -1" diffuse="1 1 1"/><body pos="0 0 1"><joint type="ball"/><geom type="capsule" size="0.06" fromto="0 0 0  0 0 -.4"/><body pos="0 0 -0.4"><joint axis="0 1 0"/><joint axis="1 0 0"/><geom type="capsule" size="0.04" fromto="0 0 0  .3 0 0"/><body pos=".3 0 0"><joint axis="0 1 0"/><joint axis="0 0 1"/><geom pos=".1 0 0" size="0.1 0.08 0.02" type="ellipsoid"/><site name="end1" pos="0.2 0 0" size="0.01"/></body></body></body><body pos="0.3 0 0.1"><joint type="free"/><geom size="0.07 0.1" type="cylinder"/><site name="end2" pos="0 0 0.1" size="0.01"/></body></worldbody><tendon><spatial limited="true" range="0 0.6" width="0.005"><site site="end1"/><site site="end2"/></spatial></tendon>
</mujoco>

        该模型是一个 7 自由度的手臂,"握 "着一根绳子,绳子的另一端连接着一个圆柱体。绳子的长度是有限制的。肩部有球形关节,肘部和腕部有一对铰链关节。圆柱体内的方框表示自由 "关节"。XML 中的外部身体元素是所需的世界体。请注意,在两个身体之间使用多个关节并不需要创建虚拟身体。

        MJCF 文件包含指定模型所需的最少信息。舱体由空间中的线段定义 —— 在这种情况下只需要舱体的半径。主体坐标系的位置和方向是根据其所属的几何体推断出来的。惯性特性是根据均匀密度假设下的几何体形状推断出来的。命名这两个部位是因为肌腱定义需要参考它们,但没有命名其他部位。仅为铰链关节定义了关节轴,但没有为球关节定义关节轴。碰撞规则是自动定义的。摩擦属性、重力、仿真时间步长等均设置为默认值。顶部指定的默认几何体颜色适用于所有几何体。

        除了以二进制 MJB 格式保存编译后的模型外,我们还可以将其保存为 MJCF 格式或人类可读文本格式;分别参见 example_saved.xml 和 example_saved.txt。XML 版本与原始版本类似,而文本版本则包含 mjModel 的所有信息。比较文本版本和 XML 版本可以看出模型编译器为我们做了多少工作。

二、模型元素

        本节简要介绍了可以包含在 MuJoCo 模型中的所有元素。稍后我们将更详细地解释基础计算、在 MJCF 中指定元素的方式以及它们在 mjModel 中的表示。

2.1 选项

        每个模型都有下面列出的三组选项。它们总是包含在内。如果 XML 文件中没有指定它们的值,则使用默认值。用户可以在每个仿真时间步之前更改这些选项的值。但在一个时间步长内,所有选项都不得更改。

2.1.1 mjOption

        该结构包含影响物理仿真的所有选项。它用于选择算法并设置其参数,启用或禁用仿真管道的不同部分,以及调整重力等系统级物理属性。

2.1.2 可视化

        此结构包含所有可视化选项。还有其他 OpenGL 渲染选项,但这些选项取决于会话,不属于模型的一部分。

2.1.3 mjStatistic

        此结构包含由编译器计算的有关模型的统计数据:平均身体质量、模型的空间范围等。包含该结构的目的是为了提供信息,同时也因为可视化程序使用它来进行自动缩放。

2.2 资产(Assets)

        资产本身不是模型元素。模型元素可以引用它们,在这种情况下,资产会以某种方式改变引用元素的属性。一个资产可以被多个模型元素引用。由于包含资产的唯一目的是对其进行引用,而引用只能通过名称来完成,因此每个资产都有一个名称(在适用的情况下可以从文件名中推断出来)。相反,常规元素的名称可以不定义。

2.2.1 网格

        MuJoCo 可以从 OBJ 文件和二进制 STL 中加载三角网格。MeshLab 等软件可用于转换其他格式。任何三角形集合都可以作为网格加载和可视化,碰撞检测器则使用凸壳。编译时有缩放网格和拟合原始几何形状的选项。网格还可用于自动推断惯性属性--将其视为三角形金字塔的结合体,并结合其质量和惯性。需要注意的是,网格没有颜色,而是使用参照几何体的材料属性对网格进行着色。相反,所有空间属性都由网格数据决定。MuJoCo 支持 OBJ 和自定义二进制文件格式的法线和纹理坐标。网格也可以直接嵌入到 XML 中。

2.2.2 蒙皮

        蒙皮网格(或称蒙皮)是指其形状可在运行时变形的网格。它们的顶点与刚体(此处称为骨骼)相连,每个顶点可以属于多个骨骼,从而使蒙皮产生平滑变形。皮肤是纯粹的可视化对象,不会影响物理特性,但可以显著增强视觉真实感。皮肤可以从自定义二进制文件中加载,也可以直接嵌入 XML 中,与网格类似。在自动生成复合柔性对象时,模型编译器也会为这些对象生成蒙皮。

2.2.3 高度域

        高度域可以从 PNG 文件(内部转换为灰度)或稍后描述的自定义二进制格式文件中加载。高度字段是海拔数据的矩形网格。编译器会将数据归一化为 [0-1] 范围。然后,高度域的实际空间范围由引用 geom 的尺寸参数决定。高度场只能从连接到世界体的 geom 引用。出于渲染和碰撞检测的目的,网格矩形会自动进行三角化处理,因此高度场会被视为三角棱镜的结合体。与这种复合物体的碰撞检测原则上可以为单个几何体对生成大量接触点。如果出现这种情况,只保留前 64 个接触点。理由是高度场应该用于模拟地形图,其空间特征与仿真中的其他对象相比较大,因此对于设计良好的模型来说,接触点的数量会很少。

2.2.4 纹理

        纹理可以从 PNG 文件加载,也可以由编译器根据用户定义的程序参数合成。还可以在创建模型时将纹理留空,然后在运行时进行更改,以便在 MuJoCo 仿真中渲染视频或创建其他动态效果。可视化器支持两种类型的纹理映射:2D 和立方体。2D 贴图适用于平面和高度场。立方体贴图适用于将纹理 "收缩 "到 3D 物体周围,而无需指定纹理坐标。它还可用于创建天空盒。立方体贴图的六个面可以从单独的图像文件中加载,也可以从一个合成图像文件中加载,或者通过重复相同的图像生成。与直接从模型元素中引用的所有其他资产不同,纹理只能从另一个资产(即材质)中引用,然后再从模型元素中引用。

2.2.5 材质

        材质用于控制地形、场地和筋的外观。这是通过引用相应模型元素中的材质来实现的。外观包括纹理贴图以及与以下 OpenGL 灯光交互的其他属性: RGBA、镜面反射、光泽度和发射。材质还可用于使物体反射。目前,反射只在平面和方框的 Z+ 面上渲染。请注意,模型元素也可以使用本地 RGBA 参数设置颜色。如果同时指定了材质和本地 RGBA,则本地定义优先。

2.3 运动树 Kinematic tree

        MuJoCo 可仿真运动通常受限的刚体集合的动力学。系统状态以关节坐标表示,各刚体明确组织成运动树。除顶层的 "世界 "体外,每个体都有一个唯一的父体。不允许运动学循环;如果需要循环关节,则应使用相等约束对其进行建模。因此,MuJoCo 模型的主干是一棵或几棵由嵌套体定义形成的运动树;一个孤立的浮动体也算一棵树。下面列出的其他几个元素都是在体中定义的,并属于该体。这与后面列出的独立元素形成鲜明对比,后者不能与单个体关联。

2.3.1 主体 Body

        主体(Body)具有质量和惯性属性,但不具有任何几何属性。相反,几何图形(或 geoms)被附加到物体上。每个主体(Body)都有两个坐标系:一个是用于定义主体(Body)以及定位其他元素的坐标系,另一个是以体的质量中心为中心并与其惯性主轴对齐的惯性系。因此,主体(Body)惯性矩阵在该坐标系中是对角线。在每个时间步骤中,MuJoCo 都会递归计算前向运动学,以全局笛卡尔坐标得出所有主体(Body)位置和方向。这为所有后续计算提供了基础。

2.3.2 关节 Joint

        关节是在主体(Body)内部定义的。它们在主体(Body)和其父主体(Body)之间创建运动自由度(DOF)。在没有关节的情况下,主体(Body)与其父主体(Body)是焊接在一起的。这与使用过度完整的笛卡尔坐标的游戏引擎相反,在游戏引擎中,关节会移除而不是增加自由度。有四种类型的关节:球形关节(ball)、滑动关节(slide)、铰链关节(hinge)和 "自由关节"(free joint),"自由关节 "可创建浮动体。一个主体(Body)可以有多个关节。这样就可以自动创建复合关节,而无需定义假体(dummy bodies)。球关节和自由关节的方向分量用单位四元数表示,MuJoCo 中的所有计算都遵循四元数的属性。

2.3.3 关节参考 Joint reference

        参考姿态是存储在 mjModel.qpos0 中的关节位置向量。它与模型初始配置时的关节数值相对应。在我们前面的例子中,肘部是以 90° 角弯曲配置创建的。但 MuJoCo 并不知道什么是肘部,因此默认情况下会将此关节配置视为数值为 0。我们可以覆盖默认行为,使用关节的 ref 属性指定初始配置对应于 90°。所有关节的参考值都会汇总到向量 mjModel.qpos0 中。每当仿真重置时,关节配置 mjData.qpos 都会设置为 mjModel.qpos0。运行时,关节位置矢量相对于参考姿势进行解释。具体来说,关节应用的空间变换量为 mjData.qpos - mjModel.qpos0。这种变换是对存储在 mjModel 主体元素中的父子平移和旋转偏移的补充。ref 属性只适用于标量关节(滑动和铰链)。对于球形关节,mjModel.qpos0 中保存的四元数始终为 (1,0,0,0),对应于空旋转。对于自由关节,浮动体的全局 3D 位置和四元数都保存在 mjModel.qpos0 中。

2.3.4 弹簧参考 Spring reference

        这是所有关节和肌腱弹簧达到静止长度的姿势。当关节配置偏离弹簧参考姿势时会产生弹簧力,弹簧力与偏离量呈线性关系。弹簧参考姿势保存在 mjModel.qpos_spring 中。对于滑动和铰链关节,弹簧参考由属性 springref 指定。对于球关节和自由关节,弹簧基准与初始模型配置相对应。

2.3.5 自由度 DOF

        自由度与关节密切相关,但并非一一对应,因为球关节和自由度关节具有多个 DOF。将关节视为指定位置信息,将自由度视为指定速度和力信息。更正式地说,关节位置是系统配置流形上的坐标,而关节速度则是当前位置处流形切线空间上的坐标。自由度具有与速度相关的特性,如摩擦损耗、阻尼、电枢惯性。作用在系统上的所有广义力都用 DOF 空间表示。相反,关节具有与位置相关的特性,如极限和弹簧刚度。用户不能直接指定自由度。相反,它们是由编译器根据关节创建的。

2.3.6 几何体 Geom

        Geoms 是刚性连接到主体(Body)的三维形状。可以将多个 Geom 附加到同一个主体(Body)上。鉴于 MuJoCo 只支持凸形几何体之间的碰撞,而创建非凸形物体的唯一方法就是将它们表示为凸形几何体的结合体,因此这一点特别有用。除了碰撞检测和随后的接触力计算外,几何体还用于渲染,以及在省略体质量和惯性时自动推断体质量和惯性。MuJoCo 支持几种原始几何形状:平面、球面、囊面、椭圆面、圆柱体、盒体。一个几何体也可以是一个网格或一个高度场;这可以通过引用相应的资产来实现。几何体具有许多影响仿真和可视化的材料属性。

2.3.7 站点 Site

        站点本质上是轻型几何体。它们代表了主体(Body)坐标系中感兴趣的位置。站点不参与碰撞检测或惯性属性的自动计算,但可用于指定传感器、肌腱路由和滑块-曲柄端点等其他对象的空间属性。

2.3.8 摄像机 Camera

        一个模型中可以定义多个摄像机。默认情况下总是有一个摄像机,用户可以在交互式可视化界面中用鼠标自由移动。不过,定义额外的摄像机也很方便,这些摄像机可以固定在世界中,也可以附着在某个物体上并随其移动。除了摄像机的位置和方向外,用户还可以调整垂直视场和瞳孔间距以进行立体渲染,以及创建立体虚拟环境所需的斜投影。在使用不完善的光学系统对真实摄像机进行建模时,可以为水平和垂直方向指定不同的焦距,并指定一个非居中的主点。

2.3.9 灯光 Light

        灯光可以固定在世界体上,也可以连接到运动体上。可视化器可访问 OpenGL(固定函数)中的完整照明模型,包括环境、漫反射和镜面反射组件、衰减和截止、位置和方向照明以及雾。灯光,或者说被灯光照亮的物体,还可以投射阴影。不过,与材质反射类似,每个投射阴影的灯光都会增加一次渲染,因此应谨慎使用这一功能。详细记录照明模型超出了本章的范围;请参阅 OpenGL 文档。请注意,除了用户在运动树中定义的灯光外,还有一个默认的头灯会随着摄像机移动。其属性可通过 mjVisual 选项进行调整。

2.4 独立

        这里我们描述的是不属于单个主体(Body)的模型元素,因此是在运动学树之外描述的。

2.4.1 肌腱 Tendon

        筋是标量长度元素,可用于驱动、施加限制和相等约束,或产生弹簧阻尼和摩擦损耗。有两种类型的筋腱:固定筋腱和空间筋腱。固定筋是(标量)关节位置的线性组合。它们可用于模拟机械耦合。空间筋的定义是通过一系列指定位置(或通过点)或环绕指定几何体的最短路径。仅支持球体和圆柱体作为包裹几何体,圆柱体在包裹时被视为无限长。为避免肌腱从包裹几何体的一侧突然跳到另一侧,用户还可以指定首选的一侧。如果肌腱路径中存在多个缠绕几何体,则必须用站点将它们分开,以避免使用迭代求解器。空间筋也可以使用滑轮分割成多个分支。

2.4.2 执行器 Actuator

        MuJoCo 提供了一个灵活的执行器模型,它有三个可独立指定的组件。它们共同决定了执行器的工作方式。通过以协调的方式指定这些组件,可以获得常见的致动器类型。这三个组件分别是传动、激活动力学和力的产生。传动装置规定了执行器与系统其他部分的连接方式;可用的类型包括关节、腱和滑块-曲柄。激活动力学可用于模拟气动或液压缸以及生物肌肉的内部激活状态;使用此类致动器可使整个系统的动力学达到三阶。力的产生机制决定了作为致动器输入的标量控制信号如何映射为标量力,而标量力又如何通过从传动装置推断出的力矩臂映射为广义力。

2.4.3 传感器 Sensor

        MuJoCo 可以生成仿真传感器数据,并将其保存在全局数组 mjData.sensordata 中。这些数据不会用于任何内部计算,而是提供给用户用于自定义计算或数据分析。可用的传感器类型包括触摸传感器、惯性测量单元(IMU)、力矩传感器、关节和肌腱位置及速度传感器、致动器位置、速度和力传感器、运动捕捉标记位置和四元数以及磁力计。其中有些需要额外的计算,有些则是从 mjData 的相应字段复制而来。还有一个用户传感器,允许用户代码在传感器数据数组中插入任何其他感兴趣的数量。MuJoCo 还具有离屏渲染功能,可以直接仿真彩色和深度摄像头传感器。这并不包括在标准传感器模型中,而是必须通过编程完成,如代码示例 simulate.cc 中所示。

2.4.4 等效约束 Equality

        在运动树结构和其中定义的关节/DOFs 已施加的约束之外,等效约束还可以施加额外的约束。它们可用于创建环形关节或一般的机械耦合模型。执行这些约束的内力与所有其他约束力一起计算。可用的相等约束类型包括:在一个点上连接两个物体(在运动树外创建一个球形关节);将两个物体焊接在一起;固定一个关节或肌腱的位置;通过三次多项式将两个关节或两条肌腱的位置耦合在一起;将挠性体(即可变形网格)的边缘约束为初始长度。

2.4.5 挠性 Flex

        柔性网格是在 MuJoCo 3.0 中添加的。它们表示可变形网格,可以是一维、二维或三维网格(因此其元素可以是胶囊、三角形或四面体)。与固定连接在单个物体上的静态形状的几何体不同,柔性元素是可变形的:它们是通过连接多个物体构建的,因此物体的位置和方向决定了运行时柔性元素的形状。这些可变形元素支持碰撞和接触力,还能产生被动力和约束力,从而保持可变形实体的形状。可自动从文件中加载网格,构建与网格顶点相对应的体,构建与网格面(或线或四面体,取决于维度)相对应的柔性元素,并获得相应的可变形网格。

2.4.6 接触对 Contact pair

        MuJoCo 中的接触生成是一个复杂的过程。检查是否有接触的几何体对(Geom pairs)有两个来源:自动邻近性测试(automated proximity tests)和其他统称为 "动态(dynamic) "的过滤器,以及模型中提供的明确几何体对(Geom pairs)列表。后者是一种单独的模型元素。由于接触涉及两个几何体的组合,明确的规范允许用户以动态机制无法实现的方式定义接触参数。这对于微调接触模型也很有用,特别是添加被激进过滤方案删除的接触对。现在,接触机制已扩展到柔性元素,可以在两个以上的物体之间创建接触互动。不过,这种碰撞是自动发生的,无法使用接触对进行微调。

2.4.7 接触排除

        这与接触配对相反:它指定了应从候选接触配对生成中排除的体对(而不是几何体(geoms))。它适用于禁用几何体之间的接触,因为这些几何体会导致不理想的永久接触。请注意,MuJoCo 有其他机制来处理这种情况(特别是如果几何体属于同一个体或属于父体和子体,它们就不会发生碰撞),但有时这些自动机制并不足够,明确的排除就变得很有必要。

2.4.8 自定义数值

        在 MuJoCo 仿真中输入自定义数字有三种方法。首先,可在 XML 中定义全局数字字段。它们有一个名称和一个实数值数组。其次,可以通过特定元素的自定义数组来扩展某些模型元素的定义。具体方法是在 XML 元素大小中设置 nuser_XXX 属性。第三,数组 mjData.userdata 不用于任何 MuJoCo 计算。用户可以在这里存储自定义计算的结果;请记住,所有随时间变化的内容都应存储在 mjData 中,而不是 mjModel 中。

2.4.9 自定义文本

        自定义文本字段可以保存在模型中。它们可用于自定义计算 —— 指定关键字命令或提供其他文本信息。但不要将它们用于注释;在编译后的模型中保存注释没有任何好处。XML 有自己的注释机制(被 MuJoCo 的解析器和编译器忽略),这更合适。

2.4.10 自定义元组

        自定义元组是 MuJoCo 模型元素的列表,可能包括其他元组。它们不被仿真器使用,但可用于指定用户代码所需的元素组。例如,可以使用图元来定义用于自定义触点处理的体对。

2.4.11 关键帧

        关键帧是仿真状态变量的快照。它包含关节位置矢量、关节速度矢量、执行器激活(如有)矢量和仿真时间矢量。模型可以包含一个关键帧库。这些关键帧可用于将系统状态重置为某个感兴趣的点。需要注意的是,关键帧并非用于在模型中存储轨迹数据,而应使用外部文件。

三、说明

读者很可能有使用其他物理仿真器和相关约定的经验,以及与 MuJoCo 不一致的一般编程实践。这有可能造成混淆。本节的目的是预先澄清最有可能引起混淆的方面;它介于常见问题解答和选定话题的教程之间。我们需要参考后面文档中涉及的材料,但下面的文字尽可能是自成一体的介绍性文字。

3.1 发散

当状态元素迅速趋向无穷大时,仿真就会出现发散。在 MuJoCo 中,这通常表现为 mjWARN_BADQACC 警告。发散是所有物理仿真的通病,并不一定表明模型有问题或仿真器有错误,而是提示我们,对于给定的积分器选择来说,时间步长太大了。在物理仿真中,速度(大时间步长)和稳定性(小时间步长)之间总是存在矛盾。一个对速度进行了良好调整的模型,其最大可能的时间步长是不发散的,这通常意味着它可以在极端条件下发散。从这个意义上说,罕见的发散情况实际上可以说明模型调试良好。在任何情况下,都可以通过减少时间步长和/或改用更稳定的积分器来防止发散。如果这种方法失败,罪魁祸首就不同了。例如,在物体初始化为穿透的模型中,巨大的排斥力可能会将它们推开并导致发散。

3.2 未指定单位

MuJoCo 没有指定基本物理单位。用户可以根据自己的选择来解释单位系统,只要它们是一致的即可。为了理解这一点,请看一个例子:重量为 1 千克、使用 1 牛顿推进器的 1 米宇宙飞船的动力与重量为 1 克、使用 1 达因推进器的 1 厘米宇宙飞船的动力是相同的。这是因为 MKS 和 CGS 都是一致的单位系统。这一特性允许用户随意缩放模型,这在仿真非常小或非常大的东西时非常有用,可以提高仿真的数值特性。

尽管如此,我们还是鼓励用户使用 MKS,因为 MuJoCo 在两个地方使用了类似 MKS 的默认值:

  • 重力的默认值是(0, 0, -9.81),相当于 MKS 中的地球表面重力。请注意,这并没有真正指定 MKS 单位系统,因为我们可能会在恩克拉多斯使用 CGS。
  • 几何体密度(用于推断物体质量和惯性)的默认值是 1000,相当于 MKS 中水的密度。

一旦选择了一致的基本单位(长度、质量、时间)系统,所有衍生单位都将与这一系统相对应,就像在维度分析中一样。例如,如果我们的模型被解释为 MKS,那么力和扭矩就分别以牛顿和牛顿-米为单位。

角度: 虽然在 MJCF 中可以使用度来指定角度(事实上度也是默认值),但 mjModel 和 mjData 中的所有角度量都以弧度表示。因此,举例来说,如果我们使用的是 MKS,陀螺仪报告的角速度将以弧度/秒为单位,而铰链接头的刚度将以牛米/弧度为单位。

3.3 意外碰撞

默认情况下,MuJoCo 会排除属于直接父子关系的体对的几何体之间的碰撞。例如,请看上面 "示例 "部分中的手臂模型:由于前臂是上臂的直接子体,因此即使胶囊 geoms 相互穿透,"肘部 "也不会发生碰撞。

但是,如果父体是一个静态体,即世界体,或者是一个相对于世界体没有任何自由度的体,那么这种排除就不适用。碰撞检测部分记录的这一行为可以防止物体从地板上掉落或穿过墙壁。然而,这种行为往往会导致以下情况:

用户注释掉浮动基体模型的根关节,也许是为了防止它坠落;现在基体被视为静态,出现了之前没有的新碰撞,用户感到困惑。有两种简单的方法可以避免这个问题:

  1. 不要移除根关节。也许只需禁用重力,并在可能的情况下添加一些流体粘度,就能防止模型过度移动。
  2. 通过设置相关的 contype 和 conaffinity 属性,或者使用接触排除指令,使用碰撞过滤明确禁用不需要的碰撞。

3.4 不面向对象

面向对象编程是一个非常有用的抽象概念,它建立在更基本(更接近硬件)的数据结构与对其进行操作的函数概念之上。对象是数据结构和函数的集合,这些数据结构和函数与一个语义实体相对应,因此它们之间的依赖性比与应用程序其他部分的依赖性更强。我们之所以不在这里使用,是因为从依赖结构来看,自然实体就是整个物理仿真器。与对象不同,我们有少量数据结构和大量对其进行操作的函数。

我们仍然使用一种分组方式,但与面向对象的方法不同。我们将模型(mjModel)与数据(mjData)分开。它们都是数据结构。模型包含描述建模物理系统恒定属性所需的一切,而数据则包含时变状态和内部计算的可重用中间结果。所有顶级函数的参数都是指向 mjModel 和 mjData 的指针。通过这种方式,我们避免了全局变量对工作区的污染和对多线程的干扰,但我们这样做的方式与面向对象编程实现相同效果的方式不同。

3.5 柔性和滑动

正如我们将在 "计算 "一章中详细解释的那样,MuJoCo 基于接触物理学和其他约束条件的数学模型。这个模型本质上是柔性的,即对约束条件施加更大的推力总会导致更大的加速度,因此可以唯一定义反向动力学。这是可取的,因为它能产生一个凸优化问题,并能进行依赖于逆动力学的分析,此外,我们在实践中需要模拟的大多数接触都具有一定的软性。然而,一旦我们允许软约束,我们实际上就创造了一种新的动力学类型,即变形动力学,现在我们必须明确这些动力学的行为方式。这就需要对接触和其他约束条件进行详细的参数化,其中涉及可根据约束条件设置的属性 solref 和 solimp,稍后将对其进行描述。

这种软模型经常令人困惑的一点是,无法避免逐渐的接触滑移。同样,摩擦接头也会在重力作用下逐渐屈服。这并不是因为求解器无法防止滑移(达到摩擦锥或摩擦损失极限),而是因为它一开始就没有试图防止滑移。回想一下,在给定约束条件下,较大的力必然导致较大的加速度。如果要完全抑制滑移,就必须违反这一关键特性。因此,如果你在仿真中看到逐渐滑移,直观的解释可能是摩擦力不足,但在 MuJoCo 中很少出现这种情况。相反,需要调整 solref 和 solimp 参数向量,以减少这种影响。增加约束阻抗(solimp 的前两个元素)以及全局 mjModel.opt.impratio 设置尤其有效。这种调整通常需要更小的时间步长来保持仿真稳定,因为它们会使非线性动力学更难进行数值积分。牛顿求解器也可以减少滑动,因为牛顿求解器一般更为精确。

在需要完全抑制滑动的情况下,可在主求解器之后运行第二个无滑动求解器。它通过忽略约束软度来更新摩擦维度的接触力。然而,当使用该选项时,MuJoCo 不再求解其设计用于求解的凸优化问题,仿真的鲁棒性可能会降低。因此,建议使用牛顿求解器、椭圆摩擦锥和较大的摩擦系数值来减少滑移。

3.6 类型、名称和 ID

如前所述,MuJoCo 支持大量模型元素。每种元素类型在 mjModel 中都有相应的部分列出其各种属性。例如,关节限制数据在数组

mjtNum* jnt_range;             // joint limits       (njnt x 2)

mjModel 中还给出了每个数组(本例中为 njnt)的大小。第一个关节点的限制首先包括在内,然后是第二个关节点的限制等。这种排序反映了 MuJoCo 中的所有矩阵都是行主格式。

可用的元素类型在 mjmodel.h 的枚举类型 mjtObj 中定义。这些枚举大多在内部使用。一个例外是 MuJoCo API 中的函数 mj_name2id 和 mj_id2name,它们将元素名称映射为整数 id,反之亦然。这些函数将元素类型作为输入。

在 XML 中命名模型元素是可选的。同一类型的两个元素(如两个关节)不能有相同的名称。只有在模型中其他地方需要引用给定元素时,才需要命名;XML 中的引用只能通过名称来完成。模型编译完成后,为方便用户使用,名称仍会存储在 mjModel 中,但不会对仿真产生进一步影响。名称对于查找相应的整数 id 以及渲染都很有用:例如,如果启用关节标签,每个关节旁边都会显示一个字符串(名称未定义的元素会被标记为 "关节 N",其中 N 是 id)。

元素的整数 id 对 MuJoCo 数据数组的索引至关重要。按照 C 语言的习惯,id 以 0 为基础。要打印名为 "肘 "的关节的范围,请执行以下操作

int jntid = mj_name2id(m, mjOBJ_JOINT, "elbow");
if( jntid>=0 )printf("(%f, %f)\n", m->jnt_range[2*jntid], m->jnt_range[2*jntid+1]);

如果未找到名称,函数将返回-1,这就是为什么要始终检查 id>=0 的原因。

3.7 主体、几何体、站点

Bodies、geoms 和 sites 是 MuJoCo 的元素,与物理世界中的刚体大致对应。那么为什么要把它们分开呢?这里解释了语义和计算方面的原因。

首先是相似性。Bodies、geoms 和 sites 都有空间坐标系(尽管物体也有第二个坐标系,它以物体的质量中心为中心,并与惯性主轴对齐)。这些坐标系的位置和方向是在每个时间步长通过前向运动学从 mjData.qpos 计算得出的。正向运动学的结果可以在 mjData 中以 xpos、xquat 和 xmat 的形式表示体,以 geom_xpos 和 geom_xmat 的形式表示geom,以 site_xpos 和 site_xmat 的形式表示站点。

现在是不同之处。主体(Body)用于构建运动树,是其他元素(包括 geoms 和 sites)的容器。主体(Body)具有空间坐标系和惯性属性,但没有与外观或碰撞几何相关的属性。这是因为这些属性并不影响物理特性(当然接触除外,但这些属性会单独处理)。如果你在机器人学教科书中看到过运动树的图解,那么这些物体通常被画成无定形的形状--以表明它们的实际形状与物理无关。

Geoms(几何基元的简称)用于指定外观和碰撞几何形状。每个几何体都属于一个物体,并与该物体刚性连接。同一个物体可以连接多个几何体。考虑到 MuJoCo 的碰撞检测器假定所有几何体都是凸面(如果网格不是凸面,它就会在内部用凸面体替换网格),这一点尤其有用。因此,如果要对一个非凸形状进行建模,就必须将其分解为多个凸几何体,并将所有几何体连接到同一个体上。在 XML 模型中,几何体也可以有质量和惯性(或者说用于计算质量和惯性的材料密度),但在模型编译器中,这只用于计算主体(Body)的质量和惯性。在实际仿真的 mjModel 中,geoms 没有惯性属性。

场地是轻型场地。它们具有相同的外观属性,但不能参与碰撞,也不能用于推断物体质量。另一方面,站点可以做一些几何体做不到的事情:它们可以指定触摸传感器的体积、IMU 传感器的附件、空间筋腱的路线、滑块-曲柄执行器的端点。这些都是空间量,但它们并不对应于具有质量或与其他实体发生碰撞的实体,这就是创建站点元素的原因。站点还可以用来指定用户感兴趣的点(或者说坐标系)。

下面的示例说明了可以将多个站点和地理坐标连接到同一个体上:在本例中,两个站点和两个几何体坐标连接到一个主体(Body)上。

<mujoco><worldbody><body pos="0 0 0"><geom type="sphere" size=".1" rgba=".9 .9 .1 1"/><geom type="capsule" pos="0 0 .1" size=".05 .1" rgba=".9 .9 .1 1"/><site type="box" pos="0 -.1 .3" size=".02 .02 .02" rgba=".9 .1 .9 1"/><site type="ellipsoid" pos="0 .1 .3" size=".02 .03 .04" rgba=".9 .1 .9 1"/></body></worldbody>
</mujoco>

该模型由 OpenGL 可视化器渲染为

请注意红色方框。这是身体惯性属性的等效惯性盒渲染,由 MuJoCo 内部生成。方框位于几何体(geoms)上方,但不在站点(sites)上方。这是因为只有几何体被用来(自动)推断物体的惯性属性。如果我们碰巧知道后者,当然可以直接指定。但通常更方便的做法是,让模型编译器使用均匀密度假设(可在 XML 中指定几何体密度;默认值为水的密度),从附着在几何体上的几何体推断出这些体的属性。

3.8 关节坐标

MuJoCo 与游戏引擎(如 ODE、Bullet、Havoc、PhysX)的主要区别之一是,MuJoCo 采用广义或关节坐标,而游戏引擎采用笛卡尔坐标,尽管 Bullet 现在支持广义坐标。这两种方法的区别可归纳如下:

关节坐标:

  • 最适合机器人等精细运动结构;
  • 关节增加了默认情况下焊接在一起的体之间的自由度;
  • 关节约束隐含在表示中,不会被违反;
  • 仿真体的位置和方向是通过正向运动学从广义坐标中获得的,不能直接操作(根体除外)。

笛卡尔坐标:

  • 最适合许多相互弹跳的体,如分子动力学和箱体堆叠;
  • 关节消除了默认情况下自由浮动体之间的自由度;
  • 关节约束以数值方式执行,可以违反;
  • 仿真体的位置和方向是显式表示的,可以直接操作,但这样会进一步违反关节约束。

如果模型中的自由浮动体也包含运动学树,那么在处理这些自由浮动体时,关节坐标可能会特别 容易混淆。下文将对此进行说明。

3.9 浮动物体

在使用关节坐标时,不能简单地将任意体的位置和方向设置为您想要的任何位置和方向。要达到这种效果,必须实现某种形式的逆运动学,计算出一组关节坐标(不一定是唯一的),正向运动学会根据这组关节坐标将物体放置到你想要的位置。

浮动体的情况则不同,即通过自由关节与世界相连的物体。这类物体的位置和方向以及线速度和角速度都在 mjData.qpos 和 mjData.qvel 中明确表示,因此可以直接操作。一般的方法是在 qpos 和 qvel 中找到体数据所在的地址。当然,qpos 和 qvel 代表的是关节而不是身体,因此需要相应的关节地址。假设在 XML 中,主体被命名为 "myfloatingbody"。必要的地址可以通过以下方式获得

int bodyid = mj_name2id(m, mjOBJ_BODY, "myfloatingbody");
int qposadr = -1, qveladr = -1;// make sure we have a floating body: it has a single free joint
if( bodyid>=0 && m->body_jntnum[bodyid]==1 && m->jnt_type[m->body_jntadr[bodyid]]==mjJNT_FREE ) {// extract the addresses from the joint specificationqposadr = m->jnt_qposadr[m->body_jntadr[bodyid]];qveladr = m->jnt_dofadr[m->body_jntadr[bodyid]];
}

如果一切顺利(即 "myfloatingbody "确实是一个浮动体),那么 qposadr 和 qveladr 就是浮动体/关节数据所在的 qpos 和 qvel 地址。位置数据是 7 个数字(三维位置和单位四元数),速度数据是 6 个数字(三维线速度和三维角速度)。现在可以将这些数字设置为所需的身体姿态和速度。

自由关节的语义如下。自由关节的线性姿态和线性速度都在全局坐标系中。自由关节的方向(四元数)也在全局坐标系中。然而,自由关节的旋转速度是在局部车身坐标系中。这与其说是一个设计决定,不如说是对四元数拓扑结构的正确使用。角速度存在于四元数切线空间中,而四元数切线空间是为某个方向局部定义的,因此坐标系局部角速度是一种自然的参数化。加速度与相应的速度定义在同一空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/587407.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无缝投屏技巧:怎样将Windows电脑屏幕共享到安卓手机?

使用屏幕共享技术的好处是多方面的。首先&#xff0c;它为远程协助提供了极大的便利。当用户遇到电脑操作难题时&#xff0c;技术支持人员可以远程查看用户的屏幕&#xff0c;实时指导解决问题&#xff0c;就像他们身临其境一样。其次&#xff0c;这种技术也为教育和培训带来了…

vue3 记录页面滚动条的位置,并在切换路由时存储或者取消

需求&#xff0c;当页面内容超出了浏览器可是屏幕的高度时&#xff0c;页面会出现滚动条。当我们滚动到某个位置时&#xff0c;操作了其他事件或者跳转了路由&#xff0c;再次回来时&#xff0c;希望还在当时滚动的位置。那我们就进行一下操作。 我是利用了会话存储 sessionSto…

读取信息boot.bin和xclbin命令

bootgen读Boot.bin命令 johnjohn-virtual-machine:~/project_zynq/kv260_image_ubuntu22.04$ bootgen -read BOOT-k26-starter-kit-202305_2022.2.bin xclbinutil读xclbin命令 johnjohn-virtual-machine:~/project_zynq/kv260_image_ubuntu22.04$ xclbinutil -i kv260-smartca…

OPPO VPC 实践探索

01 概述 一年前(20年6月)&#xff0c;OPPO云网络技术底座开始支持VPC方案&#xff0c;解决了用户担心的云上安全和虚拟实例的性能问题。我们称这个版本为VPC1.0&#xff0c;其采用了先进的智能网卡加速和VXLAN隧道隔离技术&#xff0c;实现了VPC从无到有的突破。 然而由于业务快…

车载以太网AVB交换机 gPTP透明时钟 6口 DB9接口 千兆车载以太网交换机

SW1100千兆车载以太网交换机 一、设备简要分析 8端口千兆和百兆混合车载以太网交换机&#xff0c;其中包含2个通道的1000BASE-T1接口&#xff0c;5通道100BASE-T1接口和1个通道1000BASE-T标准以太网(RJ45接口)&#xff0c;可以实现车载以太网多通道交换&#xff0c;千兆和百兆…

应用层的http和https协议

HTTP和HTTPS http和https是什么&#xff1f;http 常用的协议版本http/1.0http/1.1改进http/2.0 改进 http 和https有什么区别&#xff1f; http和https是什么&#xff1f; HTTP&#xff08;超文本传输协议&#xff09;是一种用于在网络上传输超文本数据的协议。它是一种客户端-…

使用纯注解的方式管理bean对象

前置知识&#xff1a; Component , Repository , Controller , Service 这些注解只局限于自己编写的类&#xff0c;而Bean注解能把第三方库中的类实例加入IOC容器中并交给spring管理。 其中&#xff1a; Component一般用于公共类 Repository 用于dao数据访问层 Service 用…

【数字图像处理】颜色空间的转换

颜色空间的转换 CMY 空间 CMY 颜色空间正好与 RGB 颜色空间互补&#xff0c; 即用白色减去 RGB 颜色空间中的某一颜色值就等于这种颜色在 CMY 颜色空间中的值。 { C 1 − R M 1 − G Y 1 − B \begin{cases}C1-R\\M1-G\\Y1-B\end{cases} ⎩ ⎨ ⎧​C1−RM1−GY1−B​ HSV 空…

Go项目结构整洁实现|GitHub 3.5k

一、前言 hi&#xff0c;大家好&#xff0c;这里是白泽。今天给大家分享一个GitHub &#x1f31f; 3.5k 的 Go项目&#xff1a;go-backend-clean-arch https://github.com/amitshekhariitbhu/go-backend-clean-architecture 这个项目是一位老外写的&#xff0c;通过一个 HTT…

读书笔记之人生算法(5)

算法十八关7-9 跨越出身和运气&#xff0c;实现富足与自由&#xff0c;用概率思维做好决策 7非理性 非理性&#xff1a;如何管住你的动物精神什么是理性&#xff1f;理性就是非条件反射。本杰明富兰克林曾说过如何有效说服别人&#xff1a;要诉诸利益&#xff0c;而非诉诸理性…

leetcode721. 合并账户【两种方法;并查集;dfs】

文章目录 并查集&#xff08;方法一&#xff09;dfs&#xff08;方法二&#xff09;dfs换一种写法 并查集&#xff08;方法一&#xff09; class Solution {unordered_map<string, int> index; // 每个邮箱都有一个唯一编号int root[10010]; // 并查集…

穿什么有这么重要?--装饰模式

1.1 穿什么有这么重要&#xff1f; 约会穿什么&#xff1f; "那要看你想给人家什么印象&#xff1f;是比较年轻&#xff0c;还是比较干练&#xff1b;是比较颓废&#xff0c;还是要比较阳光&#xff1b;也有可能你想给人家一种极其难忘的印象&#xff0c;那穿法又大不一样…