MuJoCo for Swift

macos-spm macos-bazel ubuntu-spm ubuntu-bazel documentation

这是 MuJoCo 物理仿真库的 Swift 绑定。

MuJoCo 是一个非常精确的基于 CPU 的物理仿真库。自从被 DeepMind 收购以来,MuJoCo 已经更容易被广泛使用。

MuJoCo 是 深度强化学习领域物理仿真的核心。 在 OpenAI Gym 中,有许多使用 MuJoCo 仿真的各种环境。

到目前为止,我只使用 https://github.com/liuliu/s4nnc/tree/main/examples 进行了少量的 DRL 尝试。 对于这些简单的机制,OpenAI Gym 还可以,我也不介意多等几分钟。 但最近,我对 Sim2Real 框架下更严肃地使用 DRL / PPO 产生了兴趣。 对于这些,通常使用 Isaac Gym 或利用多核进行环境运行。 对于后者,使用 Python 的 multiprocess 包进行处理很麻烦,但却是首选方式。

如果我们可以从 Swift 运行 MuJoCo,我们可以完全避免进入 Python,只需使用 DispatchQueue.concurrentPerform 进行环境运行。

安装

可以通过 Swift Package Manager 或 Bazel 安装 MuJoCo for Swift。

使用 Swift Package Manager 安装

您可以从此处在项目中添加 MuJoCo for Swift

.package(name: "MuJoCo", url: "https://github.com/liuliu/swift-mujoco.git", from: "2.3.0")

使用 Bazel 安装

您可以简单地将 MuJoCo for Swift 添加为 WORKSPACE 中的依赖项

git_repository(
    name = "swift-mujoco",
    commit = "3dfc5a10f3d09f96432efc7804b0aedfa866d0e3",
    remote = "https://github.com/liuliu/swift-mujoco.git",
    shallow_since = "1667838646 -0500"
)

load("@swift-mujoco//:deps.bzl", "swift_mujoco_deps")

swift_mujoco_deps()

MuJoCo for Swift 应该可以在 macOS、Linux 和 iOS 上运行。 GLContext 仅在 macOS 和 Linux 上可用。

自动接口生成

结构体 (Struct)

MuJoCo 接口生成使用了一种混合策略。 MuJoCo 的 API 严重依赖于暴露的公共 C 结构体。 MuJoCo for Swift 必须暴露这些 C 结构体。 这些 C 结构体的包装器是按照以下原则手动创建的:

三种实现方式中的哪一种在 Sources/codegen/functionExtension.swift 中进行了跟踪。 稍后,我们生成了 Sources/MjObject+Extensions.swift 将上述策略编码到 Swift 协议中。 所有 MjStruct 都符合此协议,该协议提供底层 C 结构体的关联类型,以及内存访问方法 withCTypeUnsafeMutablePointer(to:)

解析 MuJoCo 的 C 头文件,并用于生成结构体上的属性和结构体上的函数。 这些是 XX+Extensions.swiftXX+Functions.swift 文件。 为 MuJoCo 中的公共 C 结构体生成所有访问器。

可以使用以下命令重新生成它们:

bazel run Sources:codegen -- ~/workspace/swift-mujoco/Sources/ ~/workspace/mujoco/include/mujoco/*.h

特别注意确保正确解析注释并将其添加到这些扩展中。 https://liuliu.github.io/swift-mujoco 是基于这些注释生成的文档。

MjArray<T> 是一种数组类型,用于暴露 C 结构体中的静态或动态数组。 它保留了底层存储,以确保访问安全。 它还符合一组协议,因此对于 inout 参数和其他数组参数,您可以传递普通数组或 MjArray<T>(或通过 .tuple() 的 MjTuple),并期望它可以正常工作。

枚举 (Enum)

enum 会被解析并在 Mjt.swift 文件中生成。 因为没有类型注释,所以我们尽力从注释中推断哪些 int 类型是枚举。 MjtEnableBit, MjtDisableBit, MjtCatBitMjtPertBit 经过特殊处理为 OptionSet

函数 (Functions)

C 函数不限定于特定对象。 我们必须推断这些。 首先,我们尝试使用前缀推断它属于哪个 API 组:mj_ 匹配 mjXX 结构体,mjv_ 匹配 mjvXX 结构体。 我们更喜欢第一个参数,否则是最后一个参数(因为它经常用作写入目标)。 如果找不到合适的对象,它就是一个自由函数。

接口生成过程也认真对待 const 注释。 如果没有 const,这些方法会被注释为 mutating。 如果参数是没有 const 的指针,则会被注释为 inout

并非所有函数都是自动生成的,其中一些是手动实现的,因为

MjUI

在可能的情况下,我们倾向于自动接口生成。 这就是为什么我们以一种看起来很奇怪的方式保留某些 API。 MjUI 有许多与这种理念不符的怪癖。 特别是,MjuiDef 自动将 UI 控件与底层存储 pdata 相关联。 因为 pdata 是一个指针,所以它不考虑底层存储的生命周期。 引入了一组属性包装器:MjuiDefStateMjuiDefStateMap 来解决这个问题。 尽管用户仍然有责任确保底层存储的生命周期长于 MjUI 本身,但您无需处理原始的 pdata。 引入了一个 MjuiDefObjectMapper 以方便 MjStructMjuiDef 之间的直接映射。 您需要格外注意,因为 MjuiDefObjectMapper 不保证底层 MjStruct 的生命周期。 推荐的方法是将它们都分组在一个 final class 下,以确保它们的生命周期同步。

常量 (Constants)

到目前为止,只移植了与 MjUI 相关的常量。

回调 (Callbacks)

可以在 Mjcb 下找到回调。 这些是手动移植的回调,但确实支持 Swift 闭包(即捕获)。

反射 (Reflection)

MjStruct 实现了 CustomReflectable 协议。 这些应包含 C 结构体中的所有字段,但极少数例外(MjData 中的 bufferstackMjModel 中的 bufferMjUI 中的 userdata)。

字符串 (String)

MuJoCo 严重依赖静态分配的字符串。 为了更容易交互,这些字符串被公开为普通的 Swift 字符串。 来回复制的开销可能很高。 由于静态分配的字符串有硬性限制,因此如果达到此类限制,则会进行静默截断。

GLContext

引入了一个 GLContext 对象来委托 GLFW 交互。 添加了 MuJoCo 的 ./sample/ 中的 uitools.cc 的功能,以简化与 MjUI 的交互。 这个对象有点过度,因为它提供了对剪贴板、计时和拖放功能的访问。

示例 (Examples)

Examples/simulateExamples/ant 都应该提供了解 API 的良好起点。 要运行:

bazel run Examples:ant
bazel run --compilation_mode=opt Examples:simulate -- ~/workspace/swift-mujoco/Examples/assets/ant.xml

访问文档:https://liuliu.github.io/swift-mujoco。 这些文档应与 main 分支保持同步。

附录 (Appendix)

支持的 MuJoCo C API 列表

mj_defaultVFS
mj_addFileVFS
mj_makeEmptyFileVFS
mj_findFileVFS
mj_deleteFileVFS
mj_deleteVFS
mj_loadXML
mj_defaultLROpt
mj_defaultSolRefImp
mj_defaultOption
mj_defaultVisual
mj_loadModel
mj_deleteModel
mj_makeData
mj_deleteData
mjv_defaultCamera
mjv_defaultPerturb
mjv_defaultOption
mjv_defaultFigure
mjv_defaultScene
mjv_updateScene
mjv_freeScene
mjr_defaultContext
mjr_freeContext
mjui_themeSpacing
mjui_themeColor
mjui_add
mjui_addToSection
mjui_event
mjr_readPixels
mjr_drawPixels
mj_setLengthRange
mjr_findRect
mj_copyModel
mj_saveModel
mj_sizeModel
mj_copyData
mj_stackAlloc
mj_saveLastXML
mj_freeLastXML
mj_printSchema
mj_step
mj_step1
mj_step2
mj_forward
mj_inverse
mj_forwardSkip
mj_inverseSkip
mj_resetData
mj_resetDataDebug
mj_resetDataKeyframe
mj_setConst
mj_printFormattedModel
mj_printModel
mj_printFormattedData
mj_printData
mju_printMat
mju_printMatSparse
mj_fwdPosition
mj_fwdVelocity
mj_fwdActuation
mj_fwdAcceleration
mj_fwdConstraint
mj_Euler
mj_RungeKutta
mj_invPosition
mj_invVelocity
mj_invConstraint
mj_compareFwdInv
mj_sensorPos
mj_sensorVel
mj_sensorAcc
mj_energyPos
mj_energyVel
mj_checkPos
mj_checkVel
mj_checkAcc
mj_kinematics
mj_comPos
mj_camlight
mj_tendon
mj_transmission
mj_crb
mj_factorM
mj_solveM
mj_solveM2
mj_comVel
mj_passive
mj_subtreeVel
mj_rne
mj_rnePostConstraint
mj_collision
mj_makeConstraint
mj_projectConstraint
mj_referenceConstraint
mj_constraintUpdate
mj_addContact
mj_isPyramidal
mj_isSparse
mj_isDual
mj_mulJacVec
mj_mulJacTVec
mj_jac
mj_jacBody
mj_jacBodyCom
mj_jacGeom
mj_jacSite
mj_jacPointAxis
mj_name2id
mj_id2name
mj_fullM
mj_mulM
mj_mulM2
mj_addM
mj_applyFT
mj_objectVelocity
mj_objectAcceleration
mj_contactForce
mj_differentiatePos
mj_integratePos
mj_normalizeQuat
mj_local2Global
mj_getTotalmass
mj_setTotalmass
mj_version
mj_versionString
mj_ray
mj_rayHfield
mj_rayMesh
mjd_transitionFD
mju_rayGeom
mju_raySkin
mjv_room2model
mjv_model2room
mjv_cameraInModel
mjv_cameraInRoom
mjv_frustumHeight
mjv_alignToCamera
mjv_moveCamera
mjv_movePerturb
mjv_moveModel
mjv_initPerturb
mjv_applyPerturbPose
mjv_applyPerturbForce
mjv_averageCamera
mjv_select
mjv_initGeom
mjv_makeConnector
mjv_makeScene
mjv_addGeoms
mjv_makeLights
mjv_updateCamera
mjv_updateSkin
mjr_makeContext
mjr_changeFont
mjr_addAux
mjr_uploadTexture
mjr_uploadMesh
mjr_uploadHField
mjr_restoreBuffer
mjr_setBuffer
mjr_blitBuffer
mjr_setAux
mjr_blitAux
mjr_text
mjr_overlay
mjr_maxViewport
mjr_rectangle
mjr_label
mjr_figure
mjr_render
mjui_resize
mjui_update
mjui_render
mju_error
mju_error_i
mju_error_s
mju_warning
mju_warning_i
mju_warning_s
mju_clearHandlers
mj_warning
mju_writeLog
mju_type2Str
mju_warningText