OpenSim.jl and friends

code
C++
julia
Using OpenSim in Julia
Published

February 1, 2024

OpenSim is an open-source musculoskeletal modeling, simulation, and analysis program, developed in C++. OpenSim comes with bindings to Python, Java, and MATLAB, so that users can use OpenSim with a familiar programming language.

Julia is a fast, general-purpose, dynamic and composable programming language with a quickly growing ecosystem. Some important strengths of Julia in the context of academic research include:

I would like to see more biomechanists and movement scientists use and experiment with the Julia programming language, but for some people, the lack of Julia bindings to OpenSim may limit or prevent any experimentation with this new/different language. The goal of this project is to create bindings to use OpenSim from Julia.

“If you build it, they will come”

Project roadmap

The Julia package manager and package ecosystem makes it easy to install binary dependencies (e.g. shared C libraries, etc.) known as jlls2, and Julia can directly call C API functions. However, Julia cannot call/use C++ libraries directly, so a wrapper (using CxxWrap.jl/libcxxwrap) is needed before C++ libraries can be used from Julia. A wrapper library will depend on the OpenSim libraries and, in turn, all transitive dependencies. So in the process of wrapping OpenSim, jll’s for all transitive dependencies must exist as well. Furthermore, OpenSim heavily uses and exposes the Simbody API, so a prerequisite for a complete wrapper of OpenSim is a wrapper for Simbody.

deps opensim opensim ezc3d ezc3d opensim->ezc3d spdlog spdlog opensim->spdlog simbody simbody opensim->simbody docopt docopt.cpp opensim->docopt casadi casadi opensim->casadi eigen eigen opensim->eigen ipopt ipopt opensim->ipopt adolc adolc opensim->adolc colpack colpack opensim->colpack casadi->ipopt

OpenSim dependency tree

The inverse of OpenSim’s dependency tree roughly describes the prerequisites for each library (jll’s and/or wrappers).

invdeps ipopt_jll Ipopt_jll ✅ casadi_jll CasADi_jll ✅ ipopt_jll->casadi_jll opensim_jll opensim_jll ipopt_jll->opensim_jll casadi_jll->opensim_jll eigen_jll Eigen_jll ✅ eigen_jll->opensim_jll colpack_jll ColPack_jll* ☑️ colpack_jll->opensim_jll adolc_jll ADOLC_jll ✅ adolc_jll->opensim_jll jlsimbody_jll jlsimbody_jll ✅ simbodyjl Simbody.jl 🚧 jlsimbody_jll->simbodyjl jlopensim_jll jlopensim_jll jlsimbody_jll->jlopensim_jll simbody_jll simbody_jll ☑️ simbody_jll->jlsimbody_jll simbody_jll->opensim_jll opensimjl OpenSim.jl simbodyjl->opensimjl opensim_jll->jlopensim_jll docopt_jll docopt_jll docopt_jll->opensim_jll spdlog_jll spdlog_jll spdlog_jll->opensim_jll ezc3d_jll ezc3d_jll ezc3d_jll->opensim_jll jlopensim_jll->opensimjl

jll and wrapper prerequisites to implement OpenSim.jl
✅: Finished/Already exists
☑️: Basically finished, may need modification/adaptation
🚧: In progress
*Parts of Colpack that OpenSim relies on are disabled in the ColPack_jll build recipe

There are 4 major sub-projects, but the purpose and goals have parallels:

  • jlsimbody_jll/jlopensim_jll: C++ based wrappers that make the Simbody and OpenSim API usable from Julia using CxxWrap.
    • Goals (short and long-term):
      • Completely wrap the API for access from Julia
      • (long-term) Add the ability to extend Simbody/OpenSim types and functionality from Julia
    • Limitations:
      • Simbody visualizer will not be (directly) wrapped. The OpenGL libraries that Simbody visualizer depends on have not been added to the Julia Pkg registry; I’m assuming there is a good reason for this absence. I plan on exploring the feasibility of implement a Julia version of the Simbody visualizer.
  • Simbody.jl/OpenSim.jl: The Julia side interface to the Simbody/OpenSim API.
    • Goals:
      • Adapt C++ “OOP” approach to better match idiomatic Julian3 code (e.g. multiple-dispatch, etc)
      • Incorporate Julian syntactic sugar (e.g. getindex/setindex! using brackets, getproperty/setproperty using dot access, etc)
      • Add pretty-printing show methods where appropriate to faciliate interactive development
      • Documentation!
        • Import original API documentation into the docs REPL/search
        • Add additional documentation where needed where the Julian approach is different

Project status (as of October, ’24)

  • ☑️ simbody_jll
  • ✅ jlsimbody/jll: Functionally complete
    • The Simbody API is fully wrapped with the exception of the Simbody smart pointers. (Wrapping the smart pointers is a lower priority because they are not used in the Simbody API. However, they will need to be wrapped eventually because they are used extensively in OpenSim.)
  • 🚧 Simbody.jl
    Remaining TODOs:
    1. Direct 1:1 port of Simbody examples to Julia for integration testing
      • Many examples include API extension (custom types inheriting from the Simbody API) which is a long-term, lower-priority goal; these examples will not be ported at this time.
    2. Determine reasonable conventions for what APIs to re-implement with Julian syntactic sugar
      • Unclear which getter/setter methods should be converted to properties (i.e. getproperty/setproperty)
      • Many getter/setter’s are functionally indexing operations.
      • Indexing conventions: zero vs one-based indexing and how to naturally convert between the two
        • Julia defaults to one-based indexing. Code that is agnostic to the first-index is slightly more obscure and less beginner friendly. A naive wrapper level translation of subtracting one from indices would fail because many Simbody methods return indices, and there isn’t an obviously natural/foolproof way to distinguish the origin of indices and whether they need to be shifted.
      • Naming conventions
        • Leave API names alone (even though unconventional for most Julia styles) or rename?
          • If renaming, how to handle discoverability? E.g. for users already familiar with Simbody API, how to direct to the Julian name?
        • (minor) Conflicts with Julian convention for mutating functions to end with a bang (!)
      • const vs. mutable reference handling (i.e. “get” vs “upd” in Simbody API style)
        • Objects in Julia are mutable by default; the most Julian approach seems to be to default to mutable “upd” getter methods (which will be automatically converted when needed to const reference arguments)
    3. Convert Simbody examples to above determined Julian style
    • The build dependency handling in the OpenSim CMake build system is overly complex, out-of-step with modern CMake style/practices, and inconvenient to use. PR’s to refactor the CMake configuration are planned.

Footnotes

  1. This encourages reinventing-the-wheel for common (and already solved) problems and also discourages updates for any third-party code that is depended on. Both issues increase the likelihood of bugs in research code.↩︎

  2. “jll” is a play on the the Windows dynamic library extension “dll”↩︎

  3. “Julian” means idiomatic of Julia code and customs↩︎