{"action":"created","comment":{"url":"https://api.github.com/repos/opensim-org/opensim-core/pulls/comments/51910335","id":51910335,"diff_hunk":"@@ -0,0 +1,393 @@\n+/* -------------------------------------------------------------------------- *\n+ *                       OpenSim:  StatesTrajectory.cpp                       *\n+ * -------------------------------------------------------------------------- *\n+ * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *\n+ * See http://opensim.stanford.edu and the NOTICE file for more information.  *\n+ * OpenSim is developed at Stanford University and supported by the US        *\n+ * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *\n+ * through the Warrior Web program.                                           *\n+ *                                                                            *\n+ * Copyright (c) 2015 Stanford University and the Authors                     *\n+ * Author(s): Chris Dembia                                                    *\n+ *                                                                            *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\"); you may    *\n+ * not use this file except in compliance with the License. You may obtain a  *\n+ * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *\n+ *                                                                            *\n+ * Unless required by applicable law or agreed to in writing, software        *\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,          *\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *\n+ * See the License for the specific language governing permissions and        *\n+ * limitations under the License.                                             *\n+ * -------------------------------------------------------------------------- */\n+\n+#include \"StatesTrajectory.h\"\n+#include <OpenSim/Common/Storage.h>\n+#include <OpenSim/Simulation/Model/Model.h>\n+\n+using namespace OpenSim;\n+\n+size_t StatesTrajectory::getSize() const {\n+    return m_states.size();\n+}\n+\n+size_t StatesTrajectory::getIndexBefore(const double& time,\n+        const double& tolerance) {\n+    OPENSIM_THROW_IF(m_states.empty(), Exception, \"Trajectory is empty.\");\n+\n+    const auto it = getIteratorBefore(time, tolerance);\n+\n+    OPENSIM_THROW_IF(it == end(),\n+            TimeOutOfRange, time, \"before\",\n+            m_states.front().getTime(), m_states.back().getTime());\n+\n+    return it - begin();\n+}\n+\n+size_t StatesTrajectory::getIndexAfter(const double& time,\n+        const double& tolerance) {\n+    OPENSIM_THROW_IF(m_states.empty(), Exception, \"Trajectory is empty.\");\n+\n+    const auto it = getIteratorAfter(time, tolerance);\n+\n+    OPENSIM_THROW_IF(it == end(),\n+            TimeOutOfRange, time, \"after\",\n+            m_states.front().getTime(), m_states.back().getTime());\n+\n+    return it - begin();\n+}\n+\n+StatesTrajectory::IteratorRange StatesTrajectory::getBetween(\n+        const double& startTime, const double& endTime, \n+        const double& tolerance) {\n+    SimTK_APIARGCHECK2_ALWAYS(startTime <= endTime,\n+            \"StatesTrajectory\", \"getBetween\",\n+            \"startTime (%f) must be less than or equal to endTime (%f).\",\n+            startTime, endTime);\n+\n+    // If startTime < endTime < front().getTime(), then `start` points to\n+    // front(), which will cause iterating over the *entire* trajectory. To\n+    // prevent this, we have to detect if front().getTime() > endTime.\n+    if (m_states.empty() || front().getTime() > endTime) {\n+        return makeIteratorRange(end(), end());\n+    }\n+\n+    // Must add one to the last iterator since it's supposed to point past\n+    // the end.\n+    return makeIteratorRange(getIteratorAfter(startTime, tolerance),\n+                             getIteratorBefore(endTime, tolerance) + 1);\n+}\n+\n+StatesTrajectory::const_iterator\n+StatesTrajectory::getIteratorBefore(const double& time,\n+        const double& tolerance) {\n+    // Need a custom comparision function to extract the time from the state.\n+    auto compare = [](const double& t, const SimTK::State& s) {\n+                return t < s.getTime();\n+            };\n+    // upper_bound() finds the first element whose time is greater than the\n+    // given time.\n+    // TODO tolerance.\n+    auto upper = std::upper_bound(begin(), end(), time + tolerance, compare);\n+\n+    // If the first element is greater than the given time, then there are no\n+    // elements before the given time.\n+    if (upper == begin()) {\n+        return end();\n+    }\n+\n+    // We step back one element to get the last element whose time is less than\n+    // or equal to the given time.\n+    return upper - 1;\n+}\n+\n+StatesTrajectory::const_iterator\n+StatesTrajectory::getIteratorAfter(const double& time,\n+        const double& tolerance) {\n+    // Need a custom comparision function to extract the time from the state.\n+    auto compare = [](const SimTK::State& s, const double& t) {\n+                return s.getTime() < t;\n+            };\n+    // lower_bound() finds the first element whose time is greater than or\n+    // equal to the given time.\n+    // If the iterator is end(), there are no states after the given time.\n+    auto lower = std::lower_bound(begin(), end(), time - tolerance, compare);\n+\n+    return lower;\n+}\n+\n+void StatesTrajectory::append(const SimTK::State& state) {\n+    if (!m_states.empty()) {\n+\n+        SimTK_APIARGCHECK2_ALWAYS(m_states.back().getTime() <= state.getTime(),\n+                \"StatesTrajectory\", \"append\", \n+                \"New state's time (%f) must be equal to or greater than the \"\n+                \"time for the last state in the trajectory (%f).\",\n+                state.getTime(), m_states.back().getTime()\n+                );\n+\n+        // We assume the trajectory (before appending) is already consistent, \n+        // so we only need to check consistency with a single state in the\n+        // trajectory.\n+        OPENSIM_THROW_IF(!isConsistent(m_states.back(), state),\n+          InconsistentState, state.getTime());\n+    }\n+    m_states.push_back(state);\n+}\n+\n+bool StatesTrajectory::hasIntegrity() const {\n+    return isNondecreasingInTime() && isConsistent();\n+}\n+\n+bool StatesTrajectory::isNondecreasingInTime() const {\n+    // An empty or size-1 trajectory necessarily has nondecreasing times.\n+    if (getSize() <= 1) return true;\n+\n+    for (int itime = 1; itime < getSize(); ++itime) {\n+\n+        if (get(itime).getTime() < get(itime - 1).getTime()) {\n+            return false;\n+        }\n+\n+    }\n+    return true;\n+}\n+\n+bool StatesTrajectory::isConsistent() const {\n+    // An empty or size-1 trajectory is necessarily consistent.\n+    if (getSize() <= 1) return true;\n+\n+    const auto& state0 = get(0);\n+\n+    for (int itime = 1; itime < getSize(); ++itime) {\n+\n+        if (!isConsistent(state0, get(itime))) {\n+            return false;\n+        }\n+\n+    }\n+    return true;\n+}\n+\n+bool StatesTrajectory::isCompatibleWith(const Model& model) {\n+    // An empty trajectory is necessarily compatible.\n+    if (getSize() == 0) return true;\n+\n+    if (!isConsistent()) return false;\n+\n+    // Since we now know all the states are consistent with each other, we only\n+    // need to check if the first one is compatible with the model.\n+    const auto& state0 = get(0);\n+\n+    if (model.getNumStateVariables() != state0.getNY()) {\n+        return false;\n+    }\n+    if (model.getNumCoordinates() != state0.getNQ()) {\n+        return false;\n+    }\n+    if (model.getNumSpeeds() != state0.getNU()) {\n+        return false;\n+    }\n+    // TODO number of constraints.\n+\n+    return true;\n+}\n+\n+bool StatesTrajectory::isConsistent(const SimTK::State& stateA,\n+                                    const SimTK::State& stateB) {\n+    if (stateA.getNumSubsystems() != stateB.getNumSubsystems()) {\n+        return false;\n+    }\n+\n+    // State variables.\n+    if (stateA.getNQ() != stateB.getNQ()) {\n+        return false;\n+    }\n+    if (stateA.getNU() != stateB.getNU()) {\n+        return false;\n+    }\n+    if (stateA.getNZ() != stateB.getNZ()) {\n+        return false;\n+    }\n+\n+    // Constraints.\n+    if (stateA.getNQErr() != stateB.getNQErr()) {\n+        return false;\n+    }\n+    if (stateA.getNUErr() != stateB.getNUErr()) {\n+        return false;\n+    }\n+    if (stateA.getNUDotErr() != stateB.getNUDotErr()) {\n+        return false;\n+    }\n+    if (stateA.getNMultipliers() != stateB.getNMultipliers()) {\n+        return false;\n+    }\n+\n+    // Events.\n+    if (stateA.getNEventTriggers() != stateB.getNEventTriggers()) {\n+        return false;\n+    }\n+\n+    // Per-subsystem quantities.\n+    // TODO we could get rid of the total-over-subsystems checks above, but\n+    // those checks would let us exit earlier.\n+    for (SimTK::SubsystemIndex isub(0); isub < stateA.getNumSubsystems();\n+            ++isub) {\n+        if (stateA.getNQ(isub) != stateB.getNQ(isub)) {\n+            return false;\n+        }\n+        if (stateA.getNU(isub) != stateB.getNU(isub)) {\n+            return false;\n+        }\n+        if (stateA.getNZ(isub) != stateB.getNZ(isub)) {\n+            return false;\n+        }\n+        if (stateA.getNQErr(isub) != stateB.getNQErr(isub)) {\n+            return false;\n+        }\n+        if (stateA.getNUErr(isub) != stateB.getNUErr(isub)) {\n+            return false;\n+        }\n+        if (stateA.getNUDotErr(isub) != stateB.getNUDotErr(isub)) {\n+            return false;\n+        }\n+        if (stateA.getNMultipliers(isub) != stateB.getNMultipliers(isub)) {\n+            return false;\n+        }\n+        for(SimTK::Stage stage = SimTK::Stage::LowestValid;\n+                stage <= SimTK::Stage::HighestRuntime; ++stage) {\n+            if (stateA.getNEventTriggersByStage(isub, stage) !=\n+                    stateB.getNEventTriggersByStage(isub, stage)) {\n+                return false;\n+            }\n+        }\n+    }\n+    return true;\n+}\n+\n+StatesTrajectory StatesTrajectory::createFromStatesStorage(\n+        const Model& model,\n+        const Storage& sto,\n+        bool allowMissingColumns,\n+        bool allowExtraColumns) {\n+\n+    // Assemble the required objects.\n+    // ==============================\n+\n+    // This is what we'll return.\n+    StatesTrajectory states;\n+\n+    // Make a copy of the model so that we can get a corresponding state.\n+    Model localModel(model);\n+\n+    // We'll keep editing this state as we loop through time.\n+    auto state = localModel.initSystem();\n+\n+    // The labels of the columns in the storage file.\n+    const auto& stoLabels = sto.getColumnLabels();\n+    int numDependentColumns = stoLabels.getSize() - 1;\n+\n+    // Error checking.\n+    // ===============\n+    // Angular quantities must be expressed in radians.\n+    // TODO we could also manually convert the necessary coords/speeds to\n+    // radians.\n+    OPENSIM_THROW_IF(sto.isInDegrees(), StatesStorageIsInDegrees);\n+\n+    // If column labels aren't unique, It's unclear which column the user\n+    // wanted to use for the related state variable.\n+    OPENSIM_THROW_IF(!sto.storageLabelsAreUnique(),\n+            NonUniqueColumnsInStatesStorage);\n+\n+    // To be safe, we don't process storages in which individual rows are\n+    // missing values.\n+    OPENSIM_THROW_IF(numDependentColumns != sto.getSmallestNumberOfStates(),\n+            VaryingNumberOfStatesPerRow,\n+            numDependentColumns, sto.getSmallestNumberOfStates());\n+\n+    // Check if states are missing from the Storage.\n+    // ---------------------------------------------\n+    const auto& modelStateNames = localModel.getStateVariableNames();\n+    std::vector<std::string> missingColumnNames;\n+    // Also, assemble the names of the states that we will actually set in the\n+    // trajectory, along with the corresponding state index.\n+    std::map<int, std::string> statesToFillUp;\n+    for (int is = 0; is < modelStateNames.getSize(); ++is) {\n+        // getStateIndex() will check for pre-4.0 column names.\n+        const int stateIndex = sto.getStateIndex(modelStateNames[is]);\n+        if (stateIndex == -1) {\n+            missingColumnNames.push_back(modelStateNames[is]);\n+        } else {\n+            statesToFillUp[stateIndex] = modelStateNames[is];\n+        }\n+    }\n+    OPENSIM_THROW_IF(!allowMissingColumns && !missingColumnNames.empty(),\n+            MissingColumnsInStatesStorage, \n+            localModel.getName(), missingColumnNames);\n+\n+    // Check if the Storage has columns that are not states in the Model.\n+    // ------------------------------------------------------------------\n+    if (!allowExtraColumns) {\n+        if (numDependentColumns > statesToFillUp.size()) {\n+            std::vector<std::string> extraColumnNames;\n+            // We want the actual column names, not the state names; the two\n+            // might be different b/c the state names changed in v4.0.\n+            for (int ic = 1; ic < stoLabels.getSize(); ++ic) {\n+                // Has this label been marked as a model state?\n+                // stateIndex = columnIndex - 1 (b/c of the time column).\n+                if (statesToFillUp.count(ic - 1) == 0) {\n+                    extraColumnNames.push_back(stoLabels[ic]);\n+                }\n+            }\n+            OPENSIM_THROW(ExtraColumnsInStatesStorage, localModel.getName(),\n+                    extraColumnNames);\n+        }\n+    }\n+\n+\n+    // Fill up trajectory.\n+    // ===================\n+\n+    // Reserve the memory we'll need to fit all the states.\n+    states.m_states.reserve(sto.getSize());\n+\n+    // Working memory.\n+    SimTK::Vector dependentValues(numDependentColumns);\n+\n+    // Loop through all rows of the Storage.\n+    for (int itime = 0; itime < sto.getSize(); ++itime) {\n+\n+        // Extract data from Storage into the working Vector.\n+        sto.getData(itime, numDependentColumns, dependentValues);\n+\n+        // Set the correct time in the state.\n+        sto.getTime(itime, state.updTime());\n+\n+        // Put in NaNs for state variable values not in the Storage.\n+        for (const auto& stateName : missingColumnNames) {","path":"OpenSim/Simulation/StatesTrajectory.cpp","position":368,"original_position":368,"commit_id":"065f7af2236319b98d4a830f7a1004637d0bd859","original_commit_id":"065f7af2236319b98d4a830f7a1004637d0bd859","user":{"login":"aseth1","id":6494122,"avatar_url":"https://avatars.githubusercontent.com/u/6494122?v=3","gravatar_id":"","url":"https://api.github.com/users/aseth1","html_url":"https://github.com/aseth1","followers_url":"https://api.github.com/users/aseth1/followers","following_url":"https://api.github.com/users/aseth1/following{/other_user}","gists_url":"https://api.github.com/users/aseth1/gists{/gist_id}","starred_url":"https://api.github.com/users/aseth1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aseth1/subscriptions","organizations_url":"https://api.github.com/users/aseth1/orgs","repos_url":"https://api.github.com/users/aseth1/repos","events_url":"https://api.github.com/users/aseth1/events{/privacy}","received_events_url":"https://api.github.com/users/aseth1/received_events","type":"User","site_admin":false},"body":"Should this be NaN or model's default for this state value? NaN may be better so that the value is not blinded used as though it were read in - when in fact it did not exist. A StateTrajectoryBuilder could read in a StateTrajectory and produce a new with NaNs resolved based on convenient rules, like use default values and solver for equilibrium, etc ... is that what you are thinking?","created_at":"2016-02-04T17:56:57Z","updated_at":"2016-02-04T17:56:57Z","html_url":"https://github.com/opensim-org/opensim-core/pull/730#discussion_r51910335","pull_request_url":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730","_links":{"self":{"href":"https://api.github.com/repos/opensim-org/opensim-core/pulls/comments/51910335"},"html":{"href":"https://github.com/opensim-org/opensim-core/pull/730#discussion_r51910335"},"pull_request":{"href":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730"}}},"pull_request":{"url":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730","id":54688570,"html_url":"https://github.com/opensim-org/opensim-core/pull/730","diff_url":"https://github.com/opensim-org/opensim-core/pull/730.diff","patch_url":"https://github.com/opensim-org/opensim-core/pull/730.patch","issue_url":"https://api.github.com/repos/opensim-org/opensim-core/issues/730","number":730,"state":"open","locked":false,"title":"StatesTrajectory","user":{"login":"chrisdembia","id":846001,"avatar_url":"https://avatars.githubusercontent.com/u/846001?v=3","gravatar_id":"","url":"https://api.github.com/users/chrisdembia","html_url":"https://github.com/chrisdembia","followers_url":"https://api.github.com/users/chrisdembia/followers","following_url":"https://api.github.com/users/chrisdembia/following{/other_user}","gists_url":"https://api.github.com/users/chrisdembia/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisdembia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisdembia/subscriptions","organizations_url":"https://api.github.com/users/chrisdembia/orgs","repos_url":"https://api.github.com/users/chrisdembia/repos","events_url":"https://api.github.com/users/chrisdembia/events{/privacy}","received_events_url":"https://api.github.com/users/chrisdembia/received_events","type":"User","site_admin":false},"body":"I am actively working on a StatesTrajectory class. It's far from done, but I wanted to create a PR in case anyone has early feedback.\r\n\r\nEDIT: This PR is now ready for review. \r\n\r\n## Summary\r\n\r\n1. I've added a class StatesTrajectory (though there is no way to serialize/deserialize it yet).\r\n2. The class has a static method that can populate the trajectory from a states storage file.\r\n3. I've wrapped the class for scripting.\r\n4. In python, one can also iterate through a trajectory:\r\n  ```py\r\n  states = osim.StatesTrajectory.createFromStatesStorage(\r\n                   model, \"subject01_states.sto\")\r\n  for state in states:\r\n      model.calcMassCenterPosition(state)\r\n  ```\r\n\r\n5. I've written C++ and python tests.\r\n6. I also created a sandbox file that compiles and runs without issue, to serve as example code:\r\n  ```cpp\r\n     Model model(\"gait10dof18musc_subject01.osim\");\r\n     ...\r\n     auto& initState = model.initSystem();\r\n     StatesTrajectory states = simulate(model, initState, 0.05);\r\n \r\n     // Use trajectory.\r\n     // ---------------\r\n     for (const auto& state : states) {\r\n         std::cout << state.getTime() << \"s: \"\r\n                   << model.getStateVariableValue(state,\r\n                                                  \"ankle_r/ankle_angle_r/value\")\r\n                   << std::endl;\r\n     }\r\n  ```\r\n\r\n7. The doxygen for this class is available at http://myosin.sourceforge.net/730/classOpenSim_1_1StatesTrajectory.html.\r\n8. I made some small changes to Storage to support what I was doing with the trajectory.\r\n\r\n## Discussion points\r\n1. It may seem that a StatesTrajectory class belongs in Simbody. However, I talked with @sherm1 about this for a while and we decided that a sequential container of states is not too useful for simbody users, and that putting the container in OpenSim lets us make it more specific/powerful for OpenSim users. The idea is that Simbody will be able to de/serialize individual states as elements in an XML file, and StatesTrajectory's de/serialization will leverage that.\r\n2. When appending a new state to the trajectory, I check that the time of the new state is greater/equal to that of the last state. However, it's easy to alter the trajectory so that the times are not nondecreasing (e.g., `states[5].setTime(5)`). So we have 3 options: (1) drop any notion that the times are nondecreasing, (2) leave as-is with the weak enforcement, (3) find a way to truly enforce an invariant that the times are nondecreasing.\r\n3. What about creating a StatesTrajectory from kinematics storages (or kinematics and speed storages)? I don't think we should do this; instead, we should have a formal concept of \"trajectory builders\", like IK, muscle equilibrium, etc., and one could be creating a trajectory from just kinematics info (e.g., IK output).\r\n4. Should cache variables be saved in the serialization? Some cache variables would be silly to recompute (CMC controls) and thus make sense to save). This question is a little bit beyond the scope of this PR.\r\n5. Should `createFromStatesStorage()` have an option to `assemble()` each state?\r\n6. I created a method `consistent()` that checks if all the states have the same number of Q's, U's, etc. The guts of this method really belong in Simbody. Do we all agree? Should the `consistent()` method even exist?\r\n7. File extension for trajectory files? In the past I've like 'osimstates', but I ended up going with Ajay's 'ost'. We should discuss, though, since there might be even more opensim-related file extensions in the future.","created_at":"2015-12-28T22:26:34Z","updated_at":"2016-02-04T17:56:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"da992ac8c05a5f5da9f464b13e5601a0a7a23db1","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730/commits","review_comments_url":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730/comments","review_comment_url":"https://api.github.com/repos/opensim-org/opensim-core/pulls/comments{/number}","comments_url":"https://api.github.com/repos/opensim-org/opensim-core/issues/730/comments","statuses_url":"https://api.github.com/repos/opensim-org/opensim-core/statuses/065f7af2236319b98d4a830f7a1004637d0bd859","head":{"label":"opensim-org:states-trajectory","ref":"states-trajectory","sha":"065f7af2236319b98d4a830f7a1004637d0bd859","user":{"login":"opensim-org","id":4370782,"avatar_url":"https://avatars.githubusercontent.com/u/4370782?v=3","gravatar_id":"","url":"https://api.github.com/users/opensim-org","html_url":"https://github.com/opensim-org","followers_url":"https://api.github.com/users/opensim-org/followers","following_url":"https://api.github.com/users/opensim-org/following{/other_user}","gists_url":"https://api.github.com/users/opensim-org/gists{/gist_id}","starred_url":"https://api.github.com/users/opensim-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensim-org/subscriptions","organizations_url":"https://api.github.com/users/opensim-org/orgs","repos_url":"https://api.github.com/users/opensim-org/repos","events_url":"https://api.github.com/users/opensim-org/events{/privacy}","received_events_url":"https://api.github.com/users/opensim-org/received_events","type":"Organization","site_admin":false},"repo":{"id":20775600,"name":"opensim-core","full_name":"opensim-org/opensim-core","owner":{"login":"opensim-org","id":4370782,"avatar_url":"https://avatars.githubusercontent.com/u/4370782?v=3","gravatar_id":"","url":"https://api.github.com/users/opensim-org","html_url":"https://github.com/opensim-org","followers_url":"https://api.github.com/users/opensim-org/followers","following_url":"https://api.github.com/users/opensim-org/following{/other_user}","gists_url":"https://api.github.com/users/opensim-org/gists{/gist_id}","starred_url":"https://api.github.com/users/opensim-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensim-org/subscriptions","organizations_url":"https://api.github.com/users/opensim-org/orgs","repos_url":"https://api.github.com/users/opensim-org/repos","events_url":"https://api.github.com/users/opensim-org/events{/privacy}","received_events_url":"https://api.github.com/users/opensim-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/opensim-org/opensim-core","description":"SimTK OpenSim 4.0 C++ libraries and command-line applications, and Java/Python wrapping.","fork":false,"url":"https://api.github.com/repos/opensim-org/opensim-core","forks_url":"https://api.github.com/repos/opensim-org/opensim-core/forks","keys_url":"https://api.github.com/repos/opensim-org/opensim-core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opensim-org/opensim-core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opensim-org/opensim-core/teams","hooks_url":"https://api.github.com/repos/opensim-org/opensim-core/hooks","issue_events_url":"https://api.github.com/repos/opensim-org/opensim-core/issues/events{/number}","events_url":"https://api.github.com/repos/opensim-org/opensim-core/events","assignees_url":"https://api.github.com/repos/opensim-org/opensim-core/assignees{/user}","branches_url":"https://api.github.com/repos/opensim-org/opensim-core/branches{/branch}","tags_url":"https://api.github.com/repos/opensim-org/opensim-core/tags","blobs_url":"https://api.github.com/repos/opensim-org/opensim-core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opensim-org/opensim-core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opensim-org/opensim-core/git/refs{/sha}","trees_url":"https://api.github.com/repos/opensim-org/opensim-core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opensim-org/opensim-core/statuses/{sha}","languages_url":"https://api.github.com/repos/opensim-org/opensim-core/languages","stargazers_url":"https://api.github.com/repos/opensim-org/opensim-core/stargazers","contributors_url":"https://api.github.com/repos/opensim-org/opensim-core/contributors","subscribers_url":"https://api.github.com/repos/opensim-org/opensim-core/subscribers","subscription_url":"https://api.github.com/repos/opensim-org/opensim-core/subscription","commits_url":"https://api.github.com/repos/opensim-org/opensim-core/commits{/sha}","git_commits_url":"https://api.github.com/repos/opensim-org/opensim-core/git/commits{/sha}","comments_url":"https://api.github.com/repos/opensim-org/opensim-core/comments{/number}","issue_comment_url":"https://api.github.com/repos/opensim-org/opensim-core/issues/comments{/number}","contents_url":"https://api.github.com/repos/opensim-org/opensim-core/contents/{+path}","compare_url":"https://api.github.com/repos/opensim-org/opensim-core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opensim-org/opensim-core/merges","archive_url":"https://api.github.com/repos/opensim-org/opensim-core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opensim-org/opensim-core/downloads","issues_url":"https://api.github.com/repos/opensim-org/opensim-core/issues{/number}","pulls_url":"https://api.github.com/repos/opensim-org/opensim-core/pulls{/number}","milestones_url":"https://api.github.com/repos/opensim-org/opensim-core/milestones{/number}","notifications_url":"https://api.github.com/repos/opensim-org/opensim-core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opensim-org/opensim-core/labels{/name}","releases_url":"https://api.github.com/repos/opensim-org/opensim-core/releases{/id}","deployments_url":"https://api.github.com/repos/opensim-org/opensim-core/deployments","created_at":"2014-06-12T16:57:56Z","updated_at":"2016-01-29T14:01:56Z","pushed_at":"2016-02-03T21:06:04Z","git_url":"git://github.com/opensim-org/opensim-core.git","ssh_url":"git@github.com:opensim-org/opensim-core.git","clone_url":"https://github.com/opensim-org/opensim-core.git","svn_url":"https://github.com/opensim-org/opensim-core","homepage":"https://opensim.stanford.edu","size":127354,"stargazers_count":26,"watchers_count":26,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":20,"mirror_url":null,"open_issues_count":236,"forks":20,"open_issues":236,"watchers":26,"default_branch":"master"}},"base":{"label":"opensim-org:master","ref":"master","sha":"f4a03a5123a49045666cd0f40ff04b4cfa087a5e","user":{"login":"opensim-org","id":4370782,"avatar_url":"https://avatars.githubusercontent.com/u/4370782?v=3","gravatar_id":"","url":"https://api.github.com/users/opensim-org","html_url":"https://github.com/opensim-org","followers_url":"https://api.github.com/users/opensim-org/followers","following_url":"https://api.github.com/users/opensim-org/following{/other_user}","gists_url":"https://api.github.com/users/opensim-org/gists{/gist_id}","starred_url":"https://api.github.com/users/opensim-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensim-org/subscriptions","organizations_url":"https://api.github.com/users/opensim-org/orgs","repos_url":"https://api.github.com/users/opensim-org/repos","events_url":"https://api.github.com/users/opensim-org/events{/privacy}","received_events_url":"https://api.github.com/users/opensim-org/received_events","type":"Organization","site_admin":false},"repo":{"id":20775600,"name":"opensim-core","full_name":"opensim-org/opensim-core","owner":{"login":"opensim-org","id":4370782,"avatar_url":"https://avatars.githubusercontent.com/u/4370782?v=3","gravatar_id":"","url":"https://api.github.com/users/opensim-org","html_url":"https://github.com/opensim-org","followers_url":"https://api.github.com/users/opensim-org/followers","following_url":"https://api.github.com/users/opensim-org/following{/other_user}","gists_url":"https://api.github.com/users/opensim-org/gists{/gist_id}","starred_url":"https://api.github.com/users/opensim-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/opensim-org/subscriptions","organizations_url":"https://api.github.com/users/opensim-org/orgs","repos_url":"https://api.github.com/users/opensim-org/repos","events_url":"https://api.github.com/users/opensim-org/events{/privacy}","received_events_url":"https://api.github.com/users/opensim-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/opensim-org/opensim-core","description":"SimTK OpenSim 4.0 C++ libraries and command-line applications, and Java/Python wrapping.","fork":false,"url":"https://api.github.com/repos/opensim-org/opensim-core","forks_url":"https://api.github.com/repos/opensim-org/opensim-core/forks","keys_url":"https://api.github.com/repos/opensim-org/opensim-core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/opensim-org/opensim-core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/opensim-org/opensim-core/teams","hooks_url":"https://api.github.com/repos/opensim-org/opensim-core/hooks","issue_events_url":"https://api.github.com/repos/opensim-org/opensim-core/issues/events{/number}","events_url":"https://api.github.com/repos/opensim-org/opensim-core/events","assignees_url":"https://api.github.com/repos/opensim-org/opensim-core/assignees{/user}","branches_url":"https://api.github.com/repos/opensim-org/opensim-core/branches{/branch}","tags_url":"https://api.github.com/repos/opensim-org/opensim-core/tags","blobs_url":"https://api.github.com/repos/opensim-org/opensim-core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/opensim-org/opensim-core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/opensim-org/opensim-core/git/refs{/sha}","trees_url":"https://api.github.com/repos/opensim-org/opensim-core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/opensim-org/opensim-core/statuses/{sha}","languages_url":"https://api.github.com/repos/opensim-org/opensim-core/languages","stargazers_url":"https://api.github.com/repos/opensim-org/opensim-core/stargazers","contributors_url":"https://api.github.com/repos/opensim-org/opensim-core/contributors","subscribers_url":"https://api.github.com/repos/opensim-org/opensim-core/subscribers","subscription_url":"https://api.github.com/repos/opensim-org/opensim-core/subscription","commits_url":"https://api.github.com/repos/opensim-org/opensim-core/commits{/sha}","git_commits_url":"https://api.github.com/repos/opensim-org/opensim-core/git/commits{/sha}","comments_url":"https://api.github.com/repos/opensim-org/opensim-core/comments{/number}","issue_comment_url":"https://api.github.com/repos/opensim-org/opensim-core/issues/comments{/number}","contents_url":"https://api.github.com/repos/opensim-org/opensim-core/contents/{+path}","compare_url":"https://api.github.com/repos/opensim-org/opensim-core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/opensim-org/opensim-core/merges","archive_url":"https://api.github.com/repos/opensim-org/opensim-core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/opensim-org/opensim-core/downloads","issues_url":"https://api.github.com/repos/opensim-org/opensim-core/issues{/number}","pulls_url":"https://api.github.com/repos/opensim-org/opensim-core/pulls{/number}","milestones_url":"https://api.github.com/repos/opensim-org/opensim-core/milestones{/number}","notifications_url":"https://api.github.com/repos/opensim-org/opensim-core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/opensim-org/opensim-core/labels{/name}","releases_url":"https://api.github.com/repos/opensim-org/opensim-core/releases{/id}","deployments_url":"https://api.github.com/repos/opensim-org/opensim-core/deployments","created_at":"2014-06-12T16:57:56Z","updated_at":"2016-01-29T14:01:56Z","pushed_at":"2016-02-03T21:06:04Z","git_url":"git://github.com/opensim-org/opensim-core.git","ssh_url":"git@github.com:opensim-org/opensim-core.git","clone_url":"https://github.com/opensim-org/opensim-core.git","svn_url":"https://github.com/opensim-org/opensim-core","homepage":"https://opensim.stanford.edu","size":127354,"stargazers_count":26,"watchers_count":26,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":20,"mirror_url":null,"open_issues_count":236,"forks":20,"open_issues":236,"watchers":26,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730"},"html":{"href":"https://github.com/opensim-org/opensim-core/pull/730"},"issue":{"href":"https://api.github.com/repos/opensim-org/opensim-core/issues/730"},"comments":{"href":"https://api.github.com/repos/opensim-org/opensim-core/issues/730/comments"},"review_comments":{"href":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730/comments"},"review_comment":{"href":"https://api.github.com/repos/opensim-org/opensim-core/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/opensim-org/opensim-core/pulls/730/commits"},"statuses":{"href":"https://api.github.com/repos/opensim-org/opensim-core/statuses/065f7af2236319b98d4a830f7a1004637d0bd859"}}}}