OpenSim.jl and friends
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:
- Julia’s built-in package manager makes it easy to reproducibly depend on third-party libraries (including binaries). MATLAB’s lack of support for managing (external) dependencies discourages the use of third-party code1, and managing dependencies in Python is a well-established nightmare.
- Julia solve the “two-language problem”: Julia is fast, which means you don’t need to port your Julia code to C/C++ for it to be fast (unlike performance sensitive Python code). Most Julia code is written in Julia itself (much of the language itself is implemented in Julia), and this makes it easier for newcomers to contribute (to the language or other packages) because they don’t need to be familiar with Julia and C/C++.
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.
Project roadmap
The Julia package manager and package ecosystem makes it easy to install binary dependencies (e.g. shared C libraries, etc.) known as jll
s2, 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.
The inverse of OpenSim’s dependency tree roughly describes the prerequisites for each library (jll’s and/or wrappers).
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.
- Goals (short and long-term):
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
- Goals:
Project status (as of October, ’24)
- ☑️ simbody_jll
- Build recipe is complete
- Waiting on a refactoring of the Simbody CMake build system
- Summary of current build system shortcomings simbody#789
- Related PRs: simbody#790, simbody#792, simbody#795, simbody#801
- ✅ 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:- 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.
- 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 (
!
)
- Leave API names alone (even though unconventional for most Julia styles) or rename?
- 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)
- Unclear which getter/setter methods should be converted to properties (i.e.
- Convert Simbody examples to above determined Julian style
- Direct 1:1 port of Simbody examples to Julia for integration testing
-
- 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
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.↩︎
“jll” is a play on the the Windows dynamic library extension “dll”↩︎
“Julian” means idiomatic of Julia code and customs↩︎