MocoExtendProblem

OpenSim transpiles its C++ API to Java via SWIG. MATLAB runs on a JVM and can call Java directly. MEP is the glue that makes custom goals possible without rebuilding OpenSim.

How it works

MocoExtendProblem Custom Goals Interface

OpenSim Moco is a C++ framework for solving optimal control problems on musculoskeletal models. You define cost functions (goals), state constraints, and a solver, and it finds a physically plausible motion trajectory. Adding a new goal normally means modifying OpenSim's source and rebuilding the whole library — a significant barrier for labs that want to iterate quickly on novel objectives. MocoExtendProblem is a framework I wrote to get around that, and it's backed by a JOSS paper.

The mechanism that makes it possible is a chain of interop that OpenSim itself already provides. OpenSim uses SWIG to compile its C++ API into Java bindings. MATLAB runs on a JVM and has near-100% Java interop — you can instantiate Java objects and call Java methods directly from MATLAB scripts, no additional wrappers needed. So MATLAB is already talking to OpenSim through Java. The SWIG-generated Java classes include a getCPtr() method that returns the raw C++ pointer to the underlying object. MEP uses that pointer as the bridge: grab the live MocoProblem pointer from MATLAB, pass it as a uint64 to a MEX function, and use it to inject custom goals directly into the running C++ object without touching OpenSim's source.

The build script handles the rest automatically. Write a custom goal in C++, run build.m, and it compiles the goal into a shared library, generates the MEX interface, and regenerates extend_problem.m — a MATLAB class with methods for each goal, extracted by parsing the C++ headers with regex. The C++ header is the source of truth; the MATLAB side generates itself.

Direct collocation — the solver Moco uses — is worth appreciating on its own. You might think of it as curve fitting. You discretize the state and control trajectories across a mesh of time points, then solve a large nonlinear program where the equations of motion are enforced as equality constraints between adjacent mesh points. Rather than integrating forward through time and hoping the dynamics work out (shooting), you're fitting the entire trajectory at once so it satisfies the physics everywhere. It scales much better for long time horizons and stiff systems, and the formulation makes the problem structure more transparent than it sounds in the optimal control literature.

Architecture

Component Purpose Key Features
build.m Main build orchestrator CMake integration, MEX compilation, procedural class generation
custom_goals/ C++ goal implementations Template-based goal development, OpenSim 4.5+
custom_goals_compat/ Legacy goal implementations OpenSim 4.2–4.4 compatibility
utils/ Build utilities Regex parsing of C++ headers, MATLAB class generation, MEX interface creation
extend_problem.m Auto-generated MATLAB wrapper One method per goal, regenerated on every build

Each custom goal requires implementing four methods: constructProperties(), initializeOnModelImpl(), calcIntegrandImpl(), and calcGoalImpl(). Everything else — the MATLAB interface, the MEX bridge — is generated automatically.

Pre-built goals: MocoActivationGoal, MocoActivationSquaredGoal, MocoBOSGoal (base of support), MocoCOPGoal (center of pressure), MocoZMPGoal (zero-tilting moment point), MocoCoordinateAccelerationGoal, MocoCustomOutputGoal, MocoMarkerAccelerationGoal, MocoMaxCoordinateGoal, MocoMuscleStrainGoal.

Usage

Build

Setup MATLAB
% Configure MEX once
mex -setup C++

% Build from repo root — regenerates extend_problem.m and MEX binaries
build

Using custom goals in MATLAB

Goal Integration MATLAB
% Get the raw C++ pointer via SWIG's getCPtr()
cptr = uint64(problem.getCPtr(problem));

% Wrap it — MEP injects goals directly into the live C++ object
ep = extend_problem(cptr);

% Add goals — methods generated from C++ header signatures
ep.addMocoBOSGoal('bos_goal', 0.5, true, true, false);
ep.addMocoActivationGoal('activation_goal', 0.3, true, false, false);

solution = study.solve();

Complete example

Full workflow MATLAB
model = Model('gait2392.osim');

study = MocoStudy();
problem = study.updProblem();
problem.setModel(model);
problem.setTimeBounds(0, 1.0);
problem.addGoal(MocoControlGoal('control_goal'));

cptr = uint64(problem.getCPtr(problem));
ep = extend_problem(cptr);
ep.addMocoBOSGoal('base_of_support', 1.0, true, true, false);
ep.addMocoActivationSquaredGoal('activation_squared', 0.5, true, false, false);

solver = study.initCasADiSolver();
solver.set_num_mesh_intervals(50);

solution = study.solve();
solution.write('custom_goals_solution.sto');

Technical details

The code generation pipeline in build.m:

Step Implementation Output
Header parsing Regex-based setter extraction Method signatures and parameter lists
Class generation Template-based code generation extend_problem.m
MEX interface Automated MEX compilation via CMake + MSVC C++ to MATLAB bridge binaries
Library linking Dynamic library management Runtime goal loading from bin\RelWithDebInfo

Dependencies: OpenSim 4.2–4.5 with MATLAB scripting support, MATLAB 2022a+, Visual Studio 2019+ with Desktop C++ workload, CMake 3.23.3+. Windows only.

API documentation