这是 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。
您可以从此处在项目中添加 MuJoCo for Swift
.package(name: "MuJoCo", url: "https://github.com/liuliu/swift-mujoco.git", from: "2.3.0")
您可以简单地将 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 上可用。
MuJoCo 接口生成使用了一种混合策略。 MuJoCo 的 API 严重依赖于暴露的公共 C 结构体。 MuJoCo for Swift 必须暴露这些 C 结构体。 这些 C 结构体的包装器是按照以下原则手动创建的:
typealias
到其 C 结构体;public struct
,保持内存布局完全相同(可以更好地暴露某些属性,稍后会详细介绍);mj_deleteXXX
方法。 这些是带有 final class Storage
内部的 public struct
以跟踪生命周期。三种实现方式中的哪一种在 Sources/codegen/functionExtension.swift
中进行了跟踪。 稍后,我们生成了 Sources/MjObject+Extensions.swift
将上述策略编码到 Swift 协议中。 所有 MjStruct
都符合此协议,该协议提供底层 C 结构体的关联类型,以及内存访问方法 withCTypeUnsafeMutablePointer(to:)
。
解析 MuJoCo 的 C 头文件,并用于生成结构体上的属性和结构体上的函数。 这些是 XX+Extensions.swift
和 XX+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
会被解析并在 Mjt.swift
文件中生成。 因为没有类型注释,所以我们尽力从注释中推断哪些 int 类型是枚举。 MjtEnableBit
, MjtDisableBit
, MjtCatBit
和 MjtPertBit
经过特殊处理为 OptionSet
。
C 函数不限定于特定对象。 我们必须推断这些。 首先,我们尝试使用前缀推断它属于哪个 API 组:mj_
匹配 mjXX
结构体,mjv_
匹配 mjvXX
结构体。 我们更喜欢第一个参数,否则是最后一个参数(因为它经常用作写入目标)。 如果找不到合适的对象,它就是一个自由函数。
接口生成过程也认真对待 const
注释。 如果没有 const
,这些方法会被注释为 mutating
。 如果参数是没有 const
的指针,则会被注释为 inout
。
并非所有函数都是自动生成的,其中一些是手动实现的,因为
mj_loadXML
可以通过 throws
更好地转换为 Swift 错误处理;在可能的情况下,我们倾向于自动接口生成。 这就是为什么我们以一种看起来很奇怪的方式保留某些 API。 MjUI
有许多与这种理念不符的怪癖。 特别是,MjuiDef
自动将 UI 控件与底层存储 pdata
相关联。 因为 pdata
是一个指针,所以它不考虑底层存储的生命周期。 引入了一组属性包装器:MjuiDefState
、MjuiDefStateMap
来解决这个问题。 尽管用户仍然有责任确保底层存储的生命周期长于 MjUI
本身,但您无需处理原始的 pdata
。 引入了一个 MjuiDefObjectMapper
以方便 MjStruct
和 MjuiDef
之间的直接映射。 您需要格外注意,因为 MjuiDefObjectMapper
不保证底层 MjStruct
的生命周期。 推荐的方法是将它们都分组在一个 final class
下,以确保它们的生命周期同步。
到目前为止,只移植了与 MjUI
相关的常量。
可以在 Mjcb
下找到回调。 这些是手动移植的回调,但确实支持 Swift 闭包(即捕获)。
MjStruct 实现了 CustomReflectable
协议。 这些应包含 C 结构体中的所有字段,但极少数例外(MjData
中的 buffer
和 stack
、MjModel
中的 buffer
和 MjUI
中的 userdata
)。
MuJoCo 严重依赖静态分配的字符串。 为了更容易交互,这些字符串被公开为普通的 Swift 字符串。 来回复制的开销可能很高。 由于静态分配的字符串有硬性限制,因此如果达到此类限制,则会进行静默截断。
引入了一个 GLContext
对象来委托 GLFW 交互。 添加了 MuJoCo 的 ./sample/
中的 uitools.cc
的功能,以简化与 MjUI
的交互。 这个对象有点过度,因为它提供了对剪贴板、计时和拖放功能的访问。
Examples/simulate
和 Examples/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
分支保持同步。
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