{"action":"created","comment":{"url":"https://api.github.com/repos/dartsim/dart/pulls/comments/40496256","id":40496256,"diff_hunk":"@@ -0,0 +1,482 @@\n+/*\n+ * Copyright (c) 2015, Georgia Tech Research Corporation\n+ * All rights reserved.\n+ *\n+ * Author(s): Michael X. Grey <mxgrey@gatech.edu>\n+ *\n+ * Georgia Tech Graphics Lab and Humanoid Robotics Lab\n+ *\n+ * Directed by Prof. C. Karen Liu and Prof. Mike Stilman\n+ * <karenliu@cc.gatech.edu> <mstilman@cc.gatech.edu>\n+ *\n+ * This file is provided under the following \"BSD-style\" License:\n+ *   Redistribution and use in source and binary forms, with or\n+ *   without modification, are permitted provided that the following\n+ *   conditions are met:\n+ *   * Redistributions of source code must retain the above copyright\n+ *     notice, this list of conditions and the following disclaimer.\n+ *   * Redistributions in binary form must reproduce the above\n+ *     copyright notice, this list of conditions and the following\n+ *     disclaimer in the documentation and/or other materials provided\n+ *     with the distribution.\n+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ *   CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n+ *   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n+ *   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n+ *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\n+ *   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n+ *   AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n+ *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n+ *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n+ *   POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+#include <iostream>\n+\n+#include \"dart/common/Console.h\"\n+#include \"dart/optimizer/GradientDescentSolver.h\"\n+#include \"dart/optimizer/Problem.h\"\n+\n+namespace dart {\n+namespace optimizer {\n+\n+//==============================================================================\n+const std::string GradientDescentSolver::Type = \"GradientDescentSolver\";\n+\n+//==============================================================================\n+GradientDescentSolver::UniqueProperties::UniqueProperties(\n+    double _stepMultiplier,\n+    size_t _maxAttempts,\n+    size_t _perturbationStep,\n+    double _maxPerturbationFactor,\n+    double _maxRandomizationStep,\n+    double _defaultConstraintWeight,\n+    Eigen::VectorXd _eqConstraintWeights,\n+    Eigen::VectorXd _ineqConstraintWeights)\n+  : mStepSize(_stepMultiplier),\n+    mMaxAttempts(_maxAttempts),\n+    mPerturbationStep(_perturbationStep),\n+    mMaxPerturbationFactor(_maxPerturbationFactor),\n+    mMaxRandomizationStep(_maxRandomizationStep),\n+    mDefaultConstraintWeight(_defaultConstraintWeight),\n+    mEqConstraintWeights(_eqConstraintWeights),\n+    mIneqConstraintWeights(_ineqConstraintWeights)\n+{\n+  // Do nothing\n+}\n+\n+//==============================================================================\n+GradientDescentSolver::Properties::Properties(\n+    const Solver::Properties& _solverProperties,\n+    const UniqueProperties& _descentProperties)\n+  : Solver::Properties(_solverProperties),\n+    UniqueProperties(_descentProperties)\n+{\n+  // Do nothing\n+}\n+\n+//==============================================================================\n+GradientDescentSolver::GradientDescentSolver(const Properties& _properties)\n+  : Solver(_properties),\n+    mGradientP(_properties),\n+    mRD(),\n+    mMT(mRD()),\n+    mDistribution(0.0, std::nextafter(1.0, 2.0)) // This allows mDistrubtion to produce numbers in the range [0,1] inclusive\n+{\n+  // Do nothing\n+}\n+\n+//==============================================================================\n+GradientDescentSolver::GradientDescentSolver(std::shared_ptr<Problem> _problem)\n+  : Solver(_problem),\n+    mRD(),\n+    mMT(mRD()),\n+    mDistribution(0.0, std::nextafter(1.0, 2.0))\n+{\n+  // Do nothing\n+}\n+\n+//==============================================================================\n+GradientDescentSolver::~GradientDescentSolver()\n+{\n+  // Do nothing\n+}\n+\n+//==============================================================================\n+static double sign(double value)\n+{\n+  return value > 0? 1.0 : (value < 0? -1.0 : 0.0);\n+}\n+\n+//==============================================================================\n+bool GradientDescentSolver::solve()\n+{\n+  bool minimized = false;\n+  bool satisfied = false;\n+\n+  std::shared_ptr<Problem> problem = mProperties.mProblem;\n+  if(nullptr == problem)\n+  {\n+    dtwarn << \"[GradientDescentSolver::solve] Attempting to solve a nullptr \"\n+           << \"problem! We will return false.\\n\";\n+    return false;\n+  }\n+\n+  double tol = std::abs(mProperties.mTolerance);\n+  double gamma = mGradientP.mStepSize;\n+  size_t dim = problem->getDimension();\n+\n+  Eigen::VectorXd x = problem->getInitialGuess();\n+  assert(x.size() == static_cast<int>(dim));\n+\n+  Eigen::VectorXd lastx = x;\n+  Eigen::VectorXd dx(x.size());\n+  Eigen::VectorXd grad(x.size());\n+\n+  mEqConstraintCostCache.resize(problem->getNumEqConstraints());\n+  mIneqConstraintCostCache.resize(problem->getNumIneqConstraints());\n+\n+  mLastNumIterations = 0;\n+  size_t attemptCount = 0;\n+  do\n+  {\n+    size_t stepCount = 0;\n+    do\n+    {\n+      ++mLastNumIterations;\n+\n+      // Perturb the configuration if we have reached an iteration where we are\n+      // supposed to perturb it.\n+      if(mGradientP.mPerturbationStep > 0 && stepCount > 0\n+         && stepCount%mGradientP.mPerturbationStep == 0)\n+      {\n+        dx = x; // Seed the configuration randomizer with the current configuration\n+        randomizeConfiguration(dx);\n+\n+        // Step the current configuration towards the randomized configuration\n+        // proportionally to a randomized scaling factor\n+        double scale = mGradientP.mMaxPerturbationFactor*mDistribution(mMT);\n+        x += scale*(dx-x);\n+      }\n+\n+      // Check if the equality constraints are satsified\n+      satisfied = true;\n+      for(size_t i=0; i<problem->getNumEqConstraints(); ++i)\n+      {\n+        mEqConstraintCostCache[i] = problem->getEqConstraint(i)->eval(x);\n+        if(std::abs(mEqConstraintCostCache[i]) > tol)\n+          satisfied = false;\n+      }\n+\n+      // Check if the inequality constraints are satisfied\n+      for(size_t i=0; i<problem->getNumIneqConstraints(); ++i)\n+      {\n+        mIneqConstraintCostCache[i] = problem->getIneqConstraint(i)->eval(x);\n+        if(mIneqConstraintCostCache[i] > std::abs(tol))\n+          satisfied = false;\n+      }\n+\n+      Eigen::Map<Eigen::VectorXd> dxMap(dx.data(), dim);\n+      Eigen::Map<Eigen::VectorXd> gradMap(grad.data(), dim);\n+      // Compute the gradient of the objective, combined with the weighted\n+      // gradients of the softened constraints\n+      problem->getObjective()->evalGradient(x, dxMap);\n+      for(int i=0; i < static_cast<int>(problem->getNumEqConstraints()); ++i)\n+      {\n+        if(std::abs(mEqConstraintCostCache[i]) < tol)\n+          continue;\n+\n+        problem->getEqConstraint(i)->evalGradient(x, gradMap);\n+\n+        // Get the user-specified weight if available, otherwise use the default\n+        // weight value\n+        double weight = mGradientP.mEqConstraintWeights.size() > i?\n+              mGradientP.mEqConstraintWeights[i] :\n+              mGradientP.mDefaultConstraintWeight;\n+\n+        // We treat the constraint function as though we are minimizing its\n+        // absolute value. We do not want to treat it as though we are\n+        // minimizing its square, because that could adversely affect the\n+        // curvature of its derivative.\n+        dx += weight * grad * sign(mEqConstraintCostCache[i]);\n+      }\n+\n+      for(int i=0; i < static_cast<int>(problem->getNumIneqConstraints()); ++i)\n+      {\n+        if(mIneqConstraintCostCache[i] < tol)\n+          continue;\n+\n+        problem->getIneqConstraint(i)->evalGradient(x, gradMap);\n+\n+        // Get the user-specified weight if available, otherwise use the\n+        // default weight value\n+        double weight = mGradientP.mIneqConstraintWeights.size() > i?\n+              mGradientP.mIneqConstraintWeights[i] :\n+              mGradientP.mDefaultConstraintWeight;\n+\n+        dx += weight * grad;\n+      }\n+\n+      x -= gamma*dx;\n+      clampToBoundary(x);\n+\n+      if((x-lastx).norm() < tol)\n+        minimized = true;\n+      else\n+        minimized = false;\n+\n+      lastx = x;\n+      ++stepCount;\n+\n+      if(mProperties.mIterationsPerPrint > 0 &&\n+         stepCount%mProperties.mIterationsPerPrint == 0)\n+      {\n+        // TODO(MXG): Allow the user to specify the stream for the output\n+        std::cout << \"[GradientDescentSolver] Progress (attempt #\"\n+                  << attemptCount << \" | iteration #\" << stepCount << \")\\n\"\n+                  << \"cost: \" << problem->getObjective()->eval(x) << \" | \"\n+                  << (minimized? \"minimized | \" : \"not minimized | \")\n+                  << (satisfied? \"constraints satisfied | \"\n+                               : \"constraints unsatisfied | \")\n+                  << \"x: \" << x.transpose() << \"\\n\"\n+                  << \"grad: \" << dx.transpose() << std::endl;\n+      }\n+\n+      if(stepCount > mProperties.mNumMaxIterations)\n+        break;\n+\n+    } while(!minimized || !satisfied);\n+\n+    if(!minimized || !satisfied)\n+    {\n+      ++attemptCount;\n+\n+      if(mGradientP.mMaxAttempts > 0 && attemptCount >= mGradientP.mMaxAttempts)\n+        break;\n+\n+      if(attemptCount-1 < problem->getAllSeeds().size())\n+      {\n+        x = problem->getSeed(attemptCount-1);\n+      }\n+      else\n+      {\n+        randomizeConfiguration(x);\n+      }\n+    }\n+\n+  } while(!minimized || !satisfied);\n+\n+  mLastConfig = x;\n+  problem->setOptimalSolution(x);\n+  problem->setOptimumValue(problem->getObjective()->eval(x));\n+\n+  return minimized && satisfied;\n+}\n+\n+//==============================================================================\n+Eigen::VectorXd GradientDescentSolver::getLastConfiguration() const\n+{\n+  return mLastConfig;\n+}\n+\n+//==============================================================================\n+std::string GradientDescentSolver::getType() const\n+{\n+  return Type;\n+}\n+\n+//==============================================================================\n+std::shared_ptr<Solver> GradientDescentSolver::clone() const\n+{\n+  return std::make_shared<GradientDescentSolver>(\n+        getGradientDescentProperties());\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setProperties(const Properties& _properties)\n+{\n+  Solver::setProperties(_properties);\n+  setProperties(static_cast<const UniqueProperties&>(_properties));\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setProperties(const UniqueProperties& _properties)\n+{\n+  setStepSize(_properties.mStepSize);\n+  setMaxAttempts(_properties.mMaxAttempts);\n+  setPerturbationStep(_properties.mPerturbationStep);\n+  setMaxPerturbationFactor(_properties.mMaxPerturbationFactor);\n+  setDefaultConstraintWeight(_properties.mDefaultConstraintWeight);\n+  getEqConstraintWeights() = _properties.mEqConstraintWeights;\n+}\n+\n+//==============================================================================\n+GradientDescentSolver::Properties\n+GradientDescentSolver::getGradientDescentProperties() const\n+{\n+  return GradientDescentSolver::Properties(getSolverProperties(), mGradientP);\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::copy(const GradientDescentSolver& _other)\n+{\n+  if(this == &_other)\n+    return;\n+\n+  setProperties(_other.getGradientDescentProperties());\n+}\n+\n+//==============================================================================\n+GradientDescentSolver& GradientDescentSolver::operator=(\n+    const GradientDescentSolver& _other)\n+{\n+  copy(_other);\n+  return *this;\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setStepSize(double _newMultiplier)\n+{\n+  mGradientP.mStepSize = _newMultiplier;\n+}\n+\n+//==============================================================================\n+double GradientDescentSolver::getStepSize() const\n+{\n+  return mGradientP.mStepSize;\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setMaxAttempts(size_t _maxAttempts)\n+{\n+  mGradientP.mMaxAttempts = _maxAttempts;\n+}\n+\n+//==============================================================================\n+size_t GradientDescentSolver::getMaxAttempts() const\n+{\n+  return mGradientP.mMaxAttempts;\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setPerturbationStep(size_t _step)\n+{\n+  mGradientP.mPerturbationStep = _step;\n+}\n+\n+//==============================================================================\n+size_t GradientDescentSolver::getPerturbationStep() const\n+{\n+  return mGradientP.mPerturbationStep;\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setMaxPerturbationFactor(double _factor)\n+{\n+  mGradientP.mMaxPerturbationFactor = _factor;\n+}\n+\n+//==============================================================================\n+double GradientDescentSolver::getMaxPerturbationFactor() const\n+{\n+  return mGradientP.mMaxPerturbationFactor;\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::setDefaultConstraintWeight(double _newDefault)\n+{\n+  mGradientP.mDefaultConstraintWeight = _newDefault;\n+}\n+\n+//==============================================================================\n+double GradientDescentSolver::getDefaultConstraintWeight() const\n+{\n+  return mGradientP.mDefaultConstraintWeight;\n+}\n+\n+//==============================================================================\n+Eigen::VectorXd& GradientDescentSolver::getEqConstraintWeights()\n+{\n+  return mGradientP.mEqConstraintWeights;\n+}\n+\n+//==============================================================================\n+const Eigen::VectorXd& GradientDescentSolver::getEqConstraintWeights() const\n+{\n+  return mGradientP.mEqConstraintWeights;\n+}\n+\n+//==============================================================================\n+Eigen::VectorXd& GradientDescentSolver::getIneqConstraintWeights()\n+{\n+  return mGradientP.mIneqConstraintWeights;\n+}\n+\n+//==============================================================================\n+const Eigen::VectorXd& GradientDescentSolver::getIneqConstraintWeights() const\n+{\n+  return mGradientP.mIneqConstraintWeights;\n+}\n+\n+//==============================================================================\n+void GradientDescentSolver::randomizeConfiguration(Eigen::VectorXd& _x)\n+{\n+  if(nullptr == mProperties.mProblem)\n+    return;\n+\n+  if(_x.size() < static_cast<int>(mProperties.mProblem->getDimension()))\n+    _x = Eigen::VectorXd::Zero(mProperties.mProblem->getDimension());\n+\n+  for(int i=0; i<_x.size(); ++i)\n+  {\n+    double lower = mProperties.mProblem->getLowerBounds()[i];\n+    double upper = mProperties.mProblem->getUpperBounds()[i];\n+    double step = upper - lower;\n+    if(step > mGradientP.mMaxRandomizationStep)\n+    {\n+      step = 2*mGradientP.mMaxRandomizationStep;\n+      lower = _x[i] - step/2.0;","path":"dart/optimizer/GradientDescentSolver.cpp","position":441,"original_position":441,"commit_id":"5ad6ad3d67e15fb8ce05b5b98c263ace746c56c8","original_commit_id":"5ad6ad3d67e15fb8ce05b5b98c263ace746c56c8","user":{"login":"mxgrey","id":1307217,"avatar_url":"https://avatars.githubusercontent.com/u/1307217?v=3","gravatar_id":"","url":"https://api.github.com/users/mxgrey","html_url":"https://github.com/mxgrey","followers_url":"https://api.github.com/users/mxgrey/followers","following_url":"https://api.github.com/users/mxgrey/following{/other_user}","gists_url":"https://api.github.com/users/mxgrey/gists{/gist_id}","starred_url":"https://api.github.com/users/mxgrey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mxgrey/subscriptions","organizations_url":"https://api.github.com/users/mxgrey/orgs","repos_url":"https://api.github.com/users/mxgrey/repos","events_url":"https://api.github.com/users/mxgrey/events{/privacy}","received_events_url":"https://api.github.com/users/mxgrey/received_events","type":"User","site_admin":false},"body":"Yep, I was going to say that they would almost certainly compile to identical machine code, so we don't need to worry about it.","created_at":"2015-09-26T17:51:19Z","updated_at":"2015-09-26T17:51:19Z","html_url":"https://github.com/dartsim/dart/pull/461#discussion_r40496256","pull_request_url":"https://api.github.com/repos/dartsim/dart/pulls/461","_links":{"self":{"href":"https://api.github.com/repos/dartsim/dart/pulls/comments/40496256"},"html":{"href":"https://github.com/dartsim/dart/pull/461#discussion_r40496256"},"pull_request":{"href":"https://api.github.com/repos/dartsim/dart/pulls/461"}}},"pull_request":{"url":"https://api.github.com/repos/dartsim/dart/pulls/461","id":40328983,"html_url":"https://github.com/dartsim/dart/pull/461","diff_url":"https://github.com/dartsim/dart/pull/461.diff","patch_url":"https://github.com/dartsim/dart/pull/461.patch","issue_url":"https://api.github.com/repos/dartsim/dart/issues/461","number":461,"state":"open","locked":false,"title":"End Effector, Inverse Kinematics, and OpenSceneGraph","user":{"login":"mxgrey","id":1307217,"avatar_url":"https://avatars.githubusercontent.com/u/1307217?v=3","gravatar_id":"","url":"https://api.github.com/users/mxgrey","html_url":"https://github.com/mxgrey","followers_url":"https://api.github.com/users/mxgrey/followers","following_url":"https://api.github.com/users/mxgrey/following{/other_user}","gists_url":"https://api.github.com/users/mxgrey/gists{/gist_id}","starred_url":"https://api.github.com/users/mxgrey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mxgrey/subscriptions","organizations_url":"https://api.github.com/users/mxgrey/orgs","repos_url":"https://api.github.com/users/mxgrey/repos","events_url":"https://api.github.com/users/mxgrey/events{/privacy}","received_events_url":"https://api.github.com/users/mxgrey/received_events","type":"User","site_admin":false},"body":"This pull request introduces three new major features. While it's not preferable for me to dump out so many features simultaneously, this is happening because the features were all developed in parallel and tested by using each other.\r\n\r\nA very brief overview of the three new features is:\r\n\r\n### 1. End Effector\r\n\r\nWe are extending the Jacobian features of the BodyNode class to an EndEffector class which is able to rigidly attach to BodyNodes with an arbitrary transform offset. The Jacobian API of the BodyNode has been moved into a new ``JacobianNode`` base class which is inherited by both ``BodyNode`` and ``EndEffector``. The EndEffector also provides functionality for specifying support polygons, and in the future I intend to implement grasping context information into EndEffectors.\r\n\r\n### 2. Inverse Kinematics\r\n\r\nThe new Inverse Kinematics module in DART is a force to be reckoned with. With the Inverse Kinematics module, you can customize the:\r\n\r\n* Error Method\r\n* Gradient Method\r\n* Objective\r\n* Null space objective\r\n* And everything else\r\n\r\nThe way the IK module works is it sets up an ``optimizer::Problem`` with Objective and Constraint functions that it ties together. The ``Problem`` can be fully customized, but the IK module starts it out with a setup that is sufficient for most standard IK usage. The IK modules are clonable and can be cloned to operate on a new ``BodyNode``/``EndEffector``.\r\n\r\nThere is also a ``HierarchicalIK`` class that can tie together a hierarchy of individual ``InverseKinematics`` modules. Modules that have lower priority in the hierarchy will be projected through the null spaces of the modules with higher priority.\r\n\r\n### 3. OpenSceneGraph\r\n\r\nOSG is an open source C++ wrapper for OpenGL that has a very nice API and a boatload of features, like mouse picking. The new ``osgDart`` library provides an interface between DART and OSG, and offers drag-and-drop functionality for ``SimpleFrame``s and ``BodyNode``s.\r\n\r\nSome of the nitty gritty details inside osgDart are not fully documented because much of it is liable to be replaced with the upcoming ``ShapeNode`` changes, so it would feel like a waste to spend time on that. Also, all of my changes to the ``Shape`` class will be migrated into the ``ShapeNode`` class once it's implemented, so if those changes look unappealing, don't worry; they won't last long.\r\n\r\n### Other notes\r\n\r\nI've split out the nlopt, ipopt, and snopt portions of the ``optimizer`` namespace into their own libraries so that they're not hidden in the source code. I haven't updated the package management to support either of them yet, because I don't know yet how DART does package management, but I think it would be good if those could be packaged up and offered on the repos so they can be utilized without needing access to the source code.\r\n\r\nI wrote a simple ``GradientDescentSolver`` that optimizes using stochastic gradient descent. This way we have a solver native to DART instead of *requiring* a third party library. The ``GradientDescentSolver`` is what is used by default in the InverseKinematics modules.\r\n\r\nThe SNOPT extension is definitely clobbered right now. Tomorrow I'll try to take a pass at it and attempt to make it functional, but since I don't have access to SNOPT it probably still won't work even after my fix attempts, so I'll need someone with SNOPT access to get it fully functional.\r\n\r\n### Atlas Puppet\r\n\r\nA good summary of this pull request can be found in a single app: ``osgAtlasPuppet``, which you can find in ``osgDart/examples/osgAtlasPuppet.cpp``. The app lets you puppeteer an Atlas by clicking and dragging. You can see Atlas's support polygon and watch it try to solve its balance constraint.\r\n\r\nHere's a screenshot from the app: ![atlas puppet](http://i.imgur.com/Kr7eTJ0.png)\r\n\r\nThe green polygon is its support polygon, the blue sphere is its center of mass (projected onto the support polygon), and the green sphere is the centroid of the support polygon. You can also turn on interactive targets for more precise control over the end effectors: ![atlas leaning](http://i.imgur.com/rlypFun.png)\r\n\r\nYou can even toggle whether each foot can be used as a support polygon, and then have it balance on one leg: ![atlas one-leg](http://i.imgur.com/f9XAoj0.png)","created_at":"2015-07-20T07:45:19Z","updated_at":"2015-09-26T17:51:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e38da6ebafd40e4bc9f6760393bd57a32fb4b68e","assignee":null,"milestone":{"url":"https://api.github.com/repos/dartsim/dart/milestones/11","html_url":"https://github.com/dartsim/dart/milestones/DART%205.1.0","labels_url":"https://api.github.com/repos/dartsim/dart/milestones/11/labels","id":1173741,"number":11,"title":"DART 5.1.0","description":"","creator":{"login":"jslee02","id":4038467,"avatar_url":"https://avatars.githubusercontent.com/u/4038467?v=3","gravatar_id":"","url":"https://api.github.com/users/jslee02","html_url":"https://github.com/jslee02","followers_url":"https://api.github.com/users/jslee02/followers","following_url":"https://api.github.com/users/jslee02/following{/other_user}","gists_url":"https://api.github.com/users/jslee02/gists{/gist_id}","starred_url":"https://api.github.com/users/jslee02/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jslee02/subscriptions","organizations_url":"https://api.github.com/users/jslee02/orgs","repos_url":"https://api.github.com/users/jslee02/repos","events_url":"https://api.github.com/users/jslee02/events{/privacy}","received_events_url":"https://api.github.com/users/jslee02/received_events","type":"User","site_admin":false},"open_issues":5,"closed_issues":39,"state":"open","created_at":"2015-06-21T00:19:16Z","updated_at":"2015-09-26T04:35:28Z","due_on":"2015-09-28T04:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/dartsim/dart/pulls/461/commits","review_comments_url":"https://api.github.com/repos/dartsim/dart/pulls/461/comments","review_comment_url":"https://api.github.com/repos/dartsim/dart/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dartsim/dart/issues/461/comments","statuses_url":"https://api.github.com/repos/dartsim/dart/statuses/5ad6ad3d67e15fb8ce05b5b98c263ace746c56c8","head":{"label":"dartsim:grey/osg","ref":"grey/osg","sha":"5ad6ad3d67e15fb8ce05b5b98c263ace746c56c8","user":{"login":"dartsim","id":4191609,"avatar_url":"https://avatars.githubusercontent.com/u/4191609?v=3","gravatar_id":"","url":"https://api.github.com/users/dartsim","html_url":"https://github.com/dartsim","followers_url":"https://api.github.com/users/dartsim/followers","following_url":"https://api.github.com/users/dartsim/following{/other_user}","gists_url":"https://api.github.com/users/dartsim/gists{/gist_id}","starred_url":"https://api.github.com/users/dartsim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dartsim/subscriptions","organizations_url":"https://api.github.com/users/dartsim/orgs","repos_url":"https://api.github.com/users/dartsim/repos","events_url":"https://api.github.com/users/dartsim/events{/privacy}","received_events_url":"https://api.github.com/users/dartsim/received_events","type":"Organization","site_admin":false},"repo":{"id":2418592,"name":"dart","full_name":"dartsim/dart","owner":{"login":"dartsim","id":4191609,"avatar_url":"https://avatars.githubusercontent.com/u/4191609?v=3","gravatar_id":"","url":"https://api.github.com/users/dartsim","html_url":"https://github.com/dartsim","followers_url":"https://api.github.com/users/dartsim/followers","following_url":"https://api.github.com/users/dartsim/following{/other_user}","gists_url":"https://api.github.com/users/dartsim/gists{/gist_id}","starred_url":"https://api.github.com/users/dartsim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dartsim/subscriptions","organizations_url":"https://api.github.com/users/dartsim/orgs","repos_url":"https://api.github.com/users/dartsim/repos","events_url":"https://api.github.com/users/dartsim/events{/privacy}","received_events_url":"https://api.github.com/users/dartsim/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dartsim/dart","description":"Dynamic Animation and Robotics Toolkit","fork":false,"url":"https://api.github.com/repos/dartsim/dart","forks_url":"https://api.github.com/repos/dartsim/dart/forks","keys_url":"https://api.github.com/repos/dartsim/dart/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dartsim/dart/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dartsim/dart/teams","hooks_url":"https://api.github.com/repos/dartsim/dart/hooks","issue_events_url":"https://api.github.com/repos/dartsim/dart/issues/events{/number}","events_url":"https://api.github.com/repos/dartsim/dart/events","assignees_url":"https://api.github.com/repos/dartsim/dart/assignees{/user}","branches_url":"https://api.github.com/repos/dartsim/dart/branches{/branch}","tags_url":"https://api.github.com/repos/dartsim/dart/tags","blobs_url":"https://api.github.com/repos/dartsim/dart/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dartsim/dart/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dartsim/dart/git/refs{/sha}","trees_url":"https://api.github.com/repos/dartsim/dart/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dartsim/dart/statuses/{sha}","languages_url":"https://api.github.com/repos/dartsim/dart/languages","stargazers_url":"https://api.github.com/repos/dartsim/dart/stargazers","contributors_url":"https://api.github.com/repos/dartsim/dart/contributors","subscribers_url":"https://api.github.com/repos/dartsim/dart/subscribers","subscription_url":"https://api.github.com/repos/dartsim/dart/subscription","commits_url":"https://api.github.com/repos/dartsim/dart/commits{/sha}","git_commits_url":"https://api.github.com/repos/dartsim/dart/git/commits{/sha}","comments_url":"https://api.github.com/repos/dartsim/dart/comments{/number}","issue_comment_url":"https://api.github.com/repos/dartsim/dart/issues/comments{/number}","contents_url":"https://api.github.com/repos/dartsim/dart/contents/{+path}","compare_url":"https://api.github.com/repos/dartsim/dart/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dartsim/dart/merges","archive_url":"https://api.github.com/repos/dartsim/dart/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dartsim/dart/downloads","issues_url":"https://api.github.com/repos/dartsim/dart/issues{/number}","pulls_url":"https://api.github.com/repos/dartsim/dart/pulls{/number}","milestones_url":"https://api.github.com/repos/dartsim/dart/milestones{/number}","notifications_url":"https://api.github.com/repos/dartsim/dart/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dartsim/dart/labels{/name}","releases_url":"https://api.github.com/repos/dartsim/dart/releases{/id}","created_at":"2011-09-19T21:53:51Z","updated_at":"2015-09-13T13:30:59Z","pushed_at":"2015-09-26T04:35:28Z","git_url":"git://github.com/dartsim/dart.git","ssh_url":"git@github.com:dartsim/dart.git","clone_url":"https://github.com/dartsim/dart.git","svn_url":"https://github.com/dartsim/dart","homepage":"http://dartsim.github.io/","size":113965,"stargazers_count":72,"watchers_count":72,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":49,"mirror_url":null,"open_issues_count":115,"forks":49,"open_issues":115,"watchers":72,"default_branch":"master"}},"base":{"label":"dartsim:master","ref":"master","sha":"d8c44d52100200b146c66ee65caeeb0bf73e5e43","user":{"login":"dartsim","id":4191609,"avatar_url":"https://avatars.githubusercontent.com/u/4191609?v=3","gravatar_id":"","url":"https://api.github.com/users/dartsim","html_url":"https://github.com/dartsim","followers_url":"https://api.github.com/users/dartsim/followers","following_url":"https://api.github.com/users/dartsim/following{/other_user}","gists_url":"https://api.github.com/users/dartsim/gists{/gist_id}","starred_url":"https://api.github.com/users/dartsim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dartsim/subscriptions","organizations_url":"https://api.github.com/users/dartsim/orgs","repos_url":"https://api.github.com/users/dartsim/repos","events_url":"https://api.github.com/users/dartsim/events{/privacy}","received_events_url":"https://api.github.com/users/dartsim/received_events","type":"Organization","site_admin":false},"repo":{"id":2418592,"name":"dart","full_name":"dartsim/dart","owner":{"login":"dartsim","id":4191609,"avatar_url":"https://avatars.githubusercontent.com/u/4191609?v=3","gravatar_id":"","url":"https://api.github.com/users/dartsim","html_url":"https://github.com/dartsim","followers_url":"https://api.github.com/users/dartsim/followers","following_url":"https://api.github.com/users/dartsim/following{/other_user}","gists_url":"https://api.github.com/users/dartsim/gists{/gist_id}","starred_url":"https://api.github.com/users/dartsim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dartsim/subscriptions","organizations_url":"https://api.github.com/users/dartsim/orgs","repos_url":"https://api.github.com/users/dartsim/repos","events_url":"https://api.github.com/users/dartsim/events{/privacy}","received_events_url":"https://api.github.com/users/dartsim/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dartsim/dart","description":"Dynamic Animation and Robotics Toolkit","fork":false,"url":"https://api.github.com/repos/dartsim/dart","forks_url":"https://api.github.com/repos/dartsim/dart/forks","keys_url":"https://api.github.com/repos/dartsim/dart/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dartsim/dart/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dartsim/dart/teams","hooks_url":"https://api.github.com/repos/dartsim/dart/hooks","issue_events_url":"https://api.github.com/repos/dartsim/dart/issues/events{/number}","events_url":"https://api.github.com/repos/dartsim/dart/events","assignees_url":"https://api.github.com/repos/dartsim/dart/assignees{/user}","branches_url":"https://api.github.com/repos/dartsim/dart/branches{/branch}","tags_url":"https://api.github.com/repos/dartsim/dart/tags","blobs_url":"https://api.github.com/repos/dartsim/dart/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dartsim/dart/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dartsim/dart/git/refs{/sha}","trees_url":"https://api.github.com/repos/dartsim/dart/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dartsim/dart/statuses/{sha}","languages_url":"https://api.github.com/repos/dartsim/dart/languages","stargazers_url":"https://api.github.com/repos/dartsim/dart/stargazers","contributors_url":"https://api.github.com/repos/dartsim/dart/contributors","subscribers_url":"https://api.github.com/repos/dartsim/dart/subscribers","subscription_url":"https://api.github.com/repos/dartsim/dart/subscription","commits_url":"https://api.github.com/repos/dartsim/dart/commits{/sha}","git_commits_url":"https://api.github.com/repos/dartsim/dart/git/commits{/sha}","comments_url":"https://api.github.com/repos/dartsim/dart/comments{/number}","issue_comment_url":"https://api.github.com/repos/dartsim/dart/issues/comments{/number}","contents_url":"https://api.github.com/repos/dartsim/dart/contents/{+path}","compare_url":"https://api.github.com/repos/dartsim/dart/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dartsim/dart/merges","archive_url":"https://api.github.com/repos/dartsim/dart/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dartsim/dart/downloads","issues_url":"https://api.github.com/repos/dartsim/dart/issues{/number}","pulls_url":"https://api.github.com/repos/dartsim/dart/pulls{/number}","milestones_url":"https://api.github.com/repos/dartsim/dart/milestones{/number}","notifications_url":"https://api.github.com/repos/dartsim/dart/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dartsim/dart/labels{/name}","releases_url":"https://api.github.com/repos/dartsim/dart/releases{/id}","created_at":"2011-09-19T21:53:51Z","updated_at":"2015-09-13T13:30:59Z","pushed_at":"2015-09-26T04:35:28Z","git_url":"git://github.com/dartsim/dart.git","ssh_url":"git@github.com:dartsim/dart.git","clone_url":"https://github.com/dartsim/dart.git","svn_url":"https://github.com/dartsim/dart","homepage":"http://dartsim.github.io/","size":113965,"stargazers_count":72,"watchers_count":72,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":49,"mirror_url":null,"open_issues_count":115,"forks":49,"open_issues":115,"watchers":72,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dartsim/dart/pulls/461"},"html":{"href":"https://github.com/dartsim/dart/pull/461"},"issue":{"href":"https://api.github.com/repos/dartsim/dart/issues/461"},"comments":{"href":"https://api.github.com/repos/dartsim/dart/issues/461/comments"},"review_comments":{"href":"https://api.github.com/repos/dartsim/dart/pulls/461/comments"},"review_comment":{"href":"https://api.github.com/repos/dartsim/dart/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dartsim/dart/pulls/461/commits"},"statuses":{"href":"https://api.github.com/repos/dartsim/dart/statuses/5ad6ad3d67e15fb8ce05b5b98c263ace746c56c8"}}}}