diff --git a/source/RobotAPI/components/NaturalIKTest/NaturalIKTest.cpp b/source/RobotAPI/components/NaturalIKTest/NaturalIKTest.cpp
index 9d84fcf72b459f97cf17477113b4ea4b688c7d45..050bdb775a65ff522de0380c250cb7f0644a165e 100644
--- a/source/RobotAPI/components/NaturalIKTest/NaturalIKTest.cpp
+++ b/source/RobotAPI/components/NaturalIKTest/NaturalIKTest.cpp
@@ -32,6 +32,16 @@
 
 #include <random>
 
+#include <VirtualRobot/IK/ConstrainedOptimizationIK.h>
+
+#include <VirtualRobot/IK/constraints/OrientationConstraint.h>
+#include <VirtualRobot/IK/constraints/PoseConstraint.h>
+#include <VirtualRobot/IK/constraints/PositionConstraint.h>
+
+#include <RobotAPI/libraries/core/CartesianPositionController.h>
+
+#include <RobotAPI/libraries/diffik/CompositeDiffIK.h>
+
 namespace armarx
 {
     NaturalIKTestPropertyDefinitions::NaturalIKTestPropertyDefinitions(std::string prefix) :
@@ -183,7 +193,10 @@ namespace armarx
 
     struct IKStats
     {
+        IKStats(const std::string& name) : name(name) {}
+        std::string name;
         int solved = 0;
+        float duration = 0;
         std::vector<Eigen::Vector3f> elbow;
         Eigen::Vector3f averageElb()
         {
@@ -196,6 +209,51 @@ namespace armarx
         }
     };
 
+    struct SoftPositionConstraint : public VirtualRobot::PositionConstraint
+    {
+    public:
+        SoftPositionConstraint(const VirtualRobot::RobotPtr& robot, const VirtualRobot::RobotNodeSetPtr& nodeSet, const VirtualRobot::SceneObjectPtr& node, const VirtualRobot::SceneObjectPtr& tcp, const Eigen::Vector3f& target,
+                               VirtualRobot::IKSolver::CartesianSelection cartesianSelection = VirtualRobot::IKSolver::All, float tolerance = 3.0f)
+            : PositionConstraint(robot, nodeSet, node, target, cartesianSelection, tolerance), tcp(tcp)
+        {
+            optimizationFunctions.clear();
+
+            addOptimizationFunction(0, true);
+        }
+        VirtualRobot::SceneObjectPtr tcp;
+
+
+        Eigen::VectorXf optimizationGradient(unsigned int id) override
+        {
+            int size = nodeSet->getSize();
+
+            Eigen::MatrixXf J = ik->getJacobianMatrix(tcp);//.block(0, 0, 3, size);
+            //Eigen::Vector3f d = eef->getGlobalPose().block<3,1>(0,3) - target;
+
+            Eigen::FullPivLU<Eigen::MatrixXf> lu_decomp(J);
+            Eigen::MatrixXf nullspace = lu_decomp.kernel();
+
+            Eigen::VectorXf grad = PositionConstraint::optimizationGradient(id);
+
+            Eigen::VectorXf mapped = Eigen::VectorXf::Zero(grad.size());
+            for (int i = 0; i < nullspace.cols(); i++)
+            {
+                float squaredNorm = nullspace.col(i).squaredNorm();
+                // Prevent division by zero
+                if (squaredNorm > 1.0e-32f)
+                {
+                    mapped += nullspace.col(i) * nullspace.col(i).dot(grad) / nullspace.col(i).squaredNorm();
+                }
+            }
+            return mapped;
+        }
+
+        bool checkTolerances() override
+        {
+            return true;
+        }
+    };
+
     void NaturalIKTest::testTaskRun()
     {
         CMakePackageFinder finder("Armar6RT");
@@ -241,23 +299,34 @@ namespace armarx
         arm_R.tcp = rns_R->getTCP();
 
         std::vector<NaturalIK::Parameters> ikParamList;
-        std::vector<IKStats> ikStats = {IKStats(), IKStats()};
+        std::vector<IKStats> ikStats = {IKStats("NaturalIK"), IKStats("SimpleDiffIK"), IKStats("OptIK"), IKStats("DiffIK"), IKStats("CompositeIK")};
+
+        NaturalIK::Parameters p0;
+        p0.diffIKparams.resetRnsValues = false;
+        p0.diffIKparams.ikStepLengthInitial = 1;
+        p0.diffIKparams.ikStepLengthFineTune = 1;
+        p0.diffIKparams.maxJointAngleStep = 0.2f;
 
         {
-            NaturalIK::Parameters p;
+            NaturalIK::Parameters p = p0;
             p.diffIKparams.resetRnsValues = false;
             p.diffIKparams.elbowKp = 1;
             p.diffIKparams.jointLimitAvoidanceKp = 0.1;
+            p.diffIKparams.ikStepLengthInitial = 1;
             ikParamList.emplace_back(p);
         }
         {
-            NaturalIK::Parameters p;
+            NaturalIK::Parameters p = p0;
             p.diffIKparams.resetRnsValues = false;
             p.diffIKparams.elbowKp = 0;
             p.diffIKparams.jointLimitAvoidanceKp = 1;
             ikParamList.emplace_back(p);
         }
 
+        CompositeDiffIK::Parameters pc;
+        pc.resetRnsValues = false;
+
+
 
         std::random_device rd;  //Will be used to obtain a seed for the random number engine
         std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
@@ -276,27 +345,27 @@ namespace armarx
             }
         }
 
-        //rns_R->getNode(1)->setJointValue(0);
-        //rns_R->getNode(3)->setJointValue(0);
-        //rns_R->getNode(5)->setJointValue(0);
+        rns_R->getNode(1)->setJointValue(0);
+        rns_R->getNode(3)->setJointValue(0);
+        rns_R->getNode(5)->setJointValue(0);
+        initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
+        rns_R->getNode(1)->setJointValue(0);
+        rns_R->getNode(3)->setJointValue(0);
+        rns_R->getNode(5)->setJointValue(M_PI);
+        initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
+        rns_R->getNode(1)->setJointValue(0);
+        rns_R->getNode(3)->setJointValue(M_PI);
+        rns_R->getNode(5)->setJointValue(0);
+        initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
+        rns_R->getNode(1)->setJointValue(0);
+        rns_R->getNode(3)->setJointValue(M_PI);
+        rns_R->getNode(5)->setJointValue(M_PI);
         initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
-        //rns_R->getNode(1)->setJointValue(0);
-        //rns_R->getNode(3)->setJointValue(0);
-        //rns_R->getNode(5)->setJointValue(M_PI);
-        //initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
-        //rns_R->getNode(1)->setJointValue(0);
-        //rns_R->getNode(3)->setJointValue(M_PI);
-        //rns_R->getNode(5)->setJointValue(0);
-        //initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
-        //rns_R->getNode(1)->setJointValue(0);
-        //rns_R->getNode(3)->setJointValue(M_PI);
-        //rns_R->getNode(5)->setJointValue(M_PI);
-        //initialConfigurations.emplace_back(rns_R->getJointValuesEigen());
 
 
 
         std::vector<Eigen::Matrix4f> targets;
-        while (targets.size() < 1000)
+        while (targets.size() < 100)
         {
             for (size_t i = 0; i < rns_R->getSize(); i++)
             {
@@ -318,9 +387,12 @@ namespace armarx
             }
         }
 
-        for (size_t n = 0; n < targets.size(); n++)
+
+        IceUtil::Time start;
+        for (size_t i = 0; i < ikParamList.size(); i++)
         {
-            for (size_t i = 0; i < ikParamList.size(); i++)
+            start = TimeUtil::GetTime();
+            for (size_t n = 0; n < targets.size(); n++)
             {
                 for (const Eigen::VectorXf& initjv : initialConfigurations)
                 {
@@ -334,12 +406,110 @@ namespace armarx
                     }
                 }
             }
+            ikStats.at(i).duration = (TimeUtil::GetTime() - start).toSecondsDouble();
         }
+        start = TimeUtil::GetTime();
+
+        std::vector<VirtualRobot::RobotNodePtr> elbJoints_R;
+        for (int i = 0; i < 4; i++)
+        {
+            elbJoints_R.push_back(rns_R->getNode(i));
+        }
+        VirtualRobot::RobotNodeSetPtr rnsElb_R = VirtualRobot::RobotNodeSet::createRobotNodeSet(robot, "ELB_R", elbJoints_R, rns_R->getKinematicRoot(), elb_R);
+        for (size_t n = 0; n < targets.size(); n++)
+        {
+            rns_R->setJointValues(initialConfigurations.at(0));
+            VirtualRobot::ConstrainedOptimizationIK optIK(robot, rns_R);
+            //VirtualRobot::ConstraintPtr conPose(new VirtualRobot::PoseConstraint(robot, rns_R, tcp_R, targets.at(n)));
+            //optIK.addConstraint(conPose);
+            VirtualRobot::ConstraintPtr posConstraint(new VirtualRobot::PositionConstraint(robot, rns_R, tcp_R,
+                    math::Helpers::GetPosition(targets.at(n)), VirtualRobot::IKSolver::CartesianSelection::All));
+            posConstraint->setOptimizationFunctionFactor(1);
+
+            VirtualRobot::ConstraintPtr oriConstraint(new VirtualRobot::OrientationConstraint(robot, rns_R, tcp_R,
+                    math::Helpers::GetOrientation(targets.at(n)), VirtualRobot::IKSolver::CartesianSelection::All, VirtualRobot::MathTools::deg2rad(2)));
+            oriConstraint->setOptimizationFunctionFactor(1000);
+
+
+            Eigen::Vector3f elbowPos = ik_R.solveSoechtingIK(math::Helpers::GetPosition(targets.at(n))).elbow;
+
+            VirtualRobot::ConstraintPtr elbConstraint(new SoftPositionConstraint(robot, rns_R, elb_R, tcp_R,
+                    elbowPos, VirtualRobot::IKSolver::CartesianSelection::All));
+            elbConstraint->setOptimizationFunctionFactor(0.01);
+            //elbConstraint->
+
+            //ARMARX_IMPORTANT << VAROUT(elbConstraint->optimizationFunction(0));
+            //ARMARX_IMPORTANT << VAROUT(elbConstraint->optimizationGradient(0));
+
+            optIK.addConstraint(posConstraint);
+            optIK.addConstraint(oriConstraint);
+            //optIK.addConstraint(elbConstraint);
+
+            optIK.initialize();
+            bool reached = optIK.solve();
+            if (reached)
+            {
+                ikStats.at(2).solved = ikStats.at(2).solved + 1;
+                ikStats.at(2).elbow.emplace_back(elb_R->getPositionInRootFrame());
+                //float perr = CartesianPositionController::GetPositionError(targets.at(n), tcp_R);
+                //float oerr = 180 / M_PI * CartesianPositionController::GetOrientationError(targets.at(n), tcp_R);
+                //ARMARX_IMPORTANT << VAROUT(perr) << VAROUT(oerr);
+                //ARMARX_IMPORTANT << VAROUT(targets.at(n)) << VAROUT(tcp_R->getPoseInRootFrame());
+            }
+        }
+        ikStats.at(2).duration = (TimeUtil::GetTime() - start).toSecondsDouble();
+
+        start = TimeUtil::GetTime();
+        for (size_t n = 0; n < targets.size(); n++)
+        {
+            rns_R->setJointValues(initialConfigurations.at(0));
+            VirtualRobot::DifferentialIK diffIK(rns_R);
+            diffIK.setGoal(targets.at(n));
+            bool reached = diffIK.solveIK();
+            if (reached)
+            {
+                ikStats.at(3).solved = ikStats.at(3).solved + 1;
+                ikStats.at(3).elbow.emplace_back(elb_R->getPositionInRootFrame());
+            }
+        }
+        ikStats.at(3).duration = (TimeUtil::GetTime() - start).toSecondsDouble();
+
+        start = TimeUtil::GetTime();
+        for (size_t n = 0; n < targets.size(); n++)
+        {
+            for (const Eigen::VectorXf& initjv : initialConfigurations)
+            {
+                rns_R->setJointValues(initjv);
+                CompositeDiffIK ik(rns_R);
+                CompositeDiffIK::TargetPtr t(new CompositeDiffIK::Target(rns_R, tcp_R, targets.at(n), VirtualRobot::IKSolver::CartesianSelection::All));
+                ik.addTarget(t);
+                CompositeDiffIK::NullspaceJointTargetPtr nsjt(new CompositeDiffIK::NullspaceJointTarget(rns_R));
+                nsjt->set(2, 0.2, 1);
+                ik.addNullspaceGradient(nsjt);
+                CompositeDiffIK::Result result = ik.solve(pc);
+                if (result.reached)
+                {
+                    ikStats.at(4).solved = ikStats.at(4).solved + 1;
+                    ikStats.at(4).elbow.emplace_back(elb_R->getPositionInRootFrame());
+                    break;
+                }
+            }
+        }
+        ikStats.at(4).duration = (TimeUtil::GetTime() - start).toSecondsDouble();
+
+        for (size_t i = 0; i < ikStats.size(); i++)
+        {
+            ARMARX_IMPORTANT << ikStats.at(i).name << " solved: " << ikStats.at(i).solved << ", T: " << ikStats.at(i).duration;
+        }
+
+        //ARMARX_IMPORTANT << "NaturalIK solved: " << ikStats.at(0).solved << ", T: " << durations.at(0);
+        //ARMARX_IMPORTANT << "SimpleDiffIK solved: " << ikStats.at(1).solved;
+        //ARMARX_IMPORTANT << "OptIK solved: " << ikStats.at(2).solved;
+        //ARMARX_IMPORTANT << "DiffIK solved: " << ikStats.at(3).solved;
 
-        ARMARX_IMPORTANT << VAROUT(ikStats.at(0).solved);
-        ARMARX_IMPORTANT << VAROUT(ikStats.at(0).averageElb());
-        ARMARX_IMPORTANT << VAROUT(ikStats.at(1).solved);
-        ARMARX_IMPORTANT << VAROUT(ikStats.at(1).averageElb());
+        //ARMARX_IMPORTANT << VAROUT(ikStats.at(0).averageElb());
+        //ARMARX_IMPORTANT << VAROUT(ikStats.at(1).solved);
+        //ARMARX_IMPORTANT << VAROUT(ikStats.at(1).averageElb());
 
     }
 
diff --git a/source/RobotAPI/libraries/core/CartesianPositionController.cpp b/source/RobotAPI/libraries/core/CartesianPositionController.cpp
index 2765f2985a9ac0df68913aa73fa58c25bc5da51d..fb8742a15cc65b0f8614744b66dbc0f1d4222388 100644
--- a/source/RobotAPI/libraries/core/CartesianPositionController.cpp
+++ b/source/RobotAPI/libraries/core/CartesianPositionController.cpp
@@ -115,6 +115,25 @@ namespace armarx
                && (!checkOri || GetOrientationError(targetPose, tcp) < thresholdOriReached);
     }
 
+    bool CartesianPositionController::reached(const Eigen::Matrix4f& targetPose, VirtualRobot::IKSolver::CartesianSelection mode, float thresholdPosReached, float thresholdOriReached)
+    {
+        if (mode & VirtualRobot::IKSolver::Position)
+        {
+            if (GetPositionError(targetPose, tcp) > thresholdPosReached)
+            {
+                return false;
+            }
+        }
+        if (mode & VirtualRobot::IKSolver::Orientation)
+        {
+            if (GetOrientationError(targetPose, tcp) > thresholdOriReached)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
     Eigen::Vector3f CartesianPositionController::getPositionDiff(const Eigen::Matrix4f& targetPose) const
     {
         Eigen::Vector3f targetPos = targetPose.block<3, 1>(0, 3);
diff --git a/source/RobotAPI/libraries/core/CartesianPositionController.h b/source/RobotAPI/libraries/core/CartesianPositionController.h
index 5ecf2c66e3f25dcb7354930e2c4a7a888617f006..dfc8c96351c9ea53cae128339babb472e31a1c60 100644
--- a/source/RobotAPI/libraries/core/CartesianPositionController.h
+++ b/source/RobotAPI/libraries/core/CartesianPositionController.h
@@ -54,6 +54,7 @@ namespace armarx
         static float GetPositionError(const Eigen::Matrix4f& targetPose, const VirtualRobot::RobotNodePtr& tcp);
         static float GetOrientationError(const Eigen::Matrix4f& targetPose, const VirtualRobot::RobotNodePtr& tcp);
         static bool Reached(const Eigen::Matrix4f& targetPose, const VirtualRobot::RobotNodePtr& tcp, bool checkOri, float thresholdPosReached, float thresholdOriReached);
+        bool reached(const Eigen::Matrix4f& targetPose, VirtualRobot::IKSolver::CartesianSelection mode, float thresholdPosReached, float thresholdOriReached);
 
         Eigen::Vector3f getPositionDiff(const Eigen::Matrix4f& targetPose) const;
         Eigen::Vector3f getPositionDiffVec3(const Eigen::Vector3f& targetPosition) const;
diff --git a/source/RobotAPI/libraries/core/CartesianVelocityController.h b/source/RobotAPI/libraries/core/CartesianVelocityController.h
index a3a577198b9a6a80534013ef3aea1218aec7da12..1c2b0b28ee9607a989e25ca984e12fb893ce6ca4 100644
--- a/source/RobotAPI/libraries/core/CartesianVelocityController.h
+++ b/source/RobotAPI/libraries/core/CartesianVelocityController.h
@@ -63,12 +63,12 @@ namespace armarx
         Eigen::VectorXf maximumJointVelocities;
 
         void setJointCosts(const std::vector<float>& _jointCosts);
+        Eigen::VectorXf calculateRegularization(VirtualRobot::IKSolver::CartesianSelection mode);
 
     private:
         void calculateJacobis(VirtualRobot::IKSolver::CartesianSelection mode);
         Eigen::MatrixXf _jacobiWithCosts;
         Eigen::MatrixXf _inv;
-        Eigen::VectorXf calculateRegularization(VirtualRobot::IKSolver::CartesianSelection mode);
         bool clampJacobiAtJointLimits(VirtualRobot::IKSolver::CartesianSelection mode, const Eigen::VectorXf& cartesianVel, Eigen::MatrixXf& jacobi, Eigen::MatrixXf& _inv, float jointLimitCheckAccuracy = 0.001f);
         bool _considerJointLimits = true;
         float _cartesianMMRegularization;
diff --git a/source/RobotAPI/libraries/diffik/CMakeLists.txt b/source/RobotAPI/libraries/diffik/CMakeLists.txt
index 05a777679813c9f2411dbe6cce56141ab75dae77..fce8840d1b6643ea53cc63d18f2703358eb37dde 100644
--- a/source/RobotAPI/libraries/diffik/CMakeLists.txt
+++ b/source/RobotAPI/libraries/diffik/CMakeLists.txt
@@ -15,6 +15,7 @@ set(LIB_FILES
     SimpleDiffIK.cpp
     RobotPlacement.cpp
     GraspTrajectory.cpp
+    CompositeDiffIK.cpp
 )
 set(LIB_HEADERS
     NaturalDiffIK.h
@@ -22,6 +23,7 @@ set(LIB_HEADERS
     DiffIKProvider.h
     RobotPlacement.h
     GraspTrajectory.h
+    CompositeDiffIK.h
 )
 
 
diff --git a/source/RobotAPI/libraries/diffik/CompositeDiffIK.cpp b/source/RobotAPI/libraries/diffik/CompositeDiffIK.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df655bd741aaacf783425877999319f4c44028aa
--- /dev/null
+++ b/source/RobotAPI/libraries/diffik/CompositeDiffIK.cpp
@@ -0,0 +1,264 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * Copyright (C) 2012-2016, High Performance Humanoid Technologies (H2T),
+ * Karlsruhe Institute of Technology (KIT), all rights reserved.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author     Simon Ottenhaus (simon dot ottenhaus at kit dot edu)
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#include "CompositeDiffIK.h"
+#include <VirtualRobot/Robot.h>
+#include <ArmarXCore/core/exceptions/Exception.h>
+#include <VirtualRobot/math/Helpers.h>
+
+using namespace armarx;
+
+CompositeDiffIK::CompositeDiffIK(const VirtualRobot::RobotNodeSetPtr& rns)
+    : rns(rns)
+{
+    ik.reset(new VirtualRobot::DifferentialIK(rns, rns->getRobot()->getRootNode(), VirtualRobot::JacobiProvider::eSVDDamped));
+}
+
+void CompositeDiffIK::addTarget(const CompositeDiffIK::TargetPtr& target)
+{
+    targets.emplace_back(target);
+}
+
+void CompositeDiffIK::addNullspaceGradient(const CompositeDiffIK::NullspaceGradientPtr& gradient)
+{
+    nullspaceGradients.emplace_back(gradient);
+}
+
+
+CompositeDiffIK::Result CompositeDiffIK::solve(Parameters params)
+{
+    if (params.resetRnsValues)
+    {
+        for (VirtualRobot::RobotNodePtr rn : rns->getAllRobotNodes())
+        {
+            if (rn->isLimitless())
+            {
+                rn->setJointValue(0);
+            }
+            else
+            {
+                rn->setJointValue((rn->getJointLimitHi() + rn->getJointLimitLo()) / 2);
+            }
+        }
+    }
+
+    int rows = 0;
+    int cols = rns->getSize();
+
+    for (const TargetPtr& target : targets)
+    {
+        rows += CartesianSelectionToSize(target->mode);
+    }
+
+    Eigen::VectorXf regularization = Eigen::VectorXf::Zero(rows);
+    {
+        CartesianVelocityController tmpVC(rns);
+        int row = 0;
+        for (const TargetPtr& target : targets)
+        {
+            int h = CartesianSelectionToSize(target->mode);
+            target->jacobi = Eigen::MatrixXf::Zero(h, cols);
+            regularization.block(row, 0, h, 1) = tmpVC.calculateRegularization(target->mode);
+            row += h;
+        }
+    }
+
+    Eigen::VectorXf currentJointValues = rns->getJointValuesEigen();
+
+    for (size_t i = 0; i <= params.steps; i++)
+    {
+        Eigen::MatrixXf jacobi = Eigen::MatrixXf::Zero(rows, cols);
+        Eigen::MatrixXf invJac = Eigen::MatrixXf::Zero(cols, rows);
+
+        Eigen::VectorXf cartesianVel = Eigen::VectorXf::Zero(rows);
+        {
+            int row = 0;
+            for (const TargetPtr& target : targets)
+            {
+                ik->updateJacobianMatrix(target->jacobi, target->tcp, target->mode);
+                int h = CartesianSelectionToSize(target->mode);
+                jacobi.block(row, 0, h, cols) = target->jacobi;
+                cartesianVel.block(row, 0, h, 1) = target->pCtrl.calculate(target->target, target->mode);
+                row += h;
+            }
+        }
+
+        ik->updatePseudoInverseJacobianMatrix(invJac, jacobi, 0, regularization);
+
+        Eigen::VectorXf nullspaceVel = Eigen::VectorXf::Zero(cols);
+
+        for (const NullspaceGradientPtr& nsGradient : nullspaceGradients)
+        {
+            nullspaceVel += nsGradient->kP * nsGradient->getGradient();
+        }
+        LimitInfNormTo(nullspaceVel, params.maxJointAngleStep);
+
+        Eigen::FullPivLU<Eigen::MatrixXf> lu_decomp(jacobi);
+        Eigen::MatrixXf nullspace = lu_decomp.kernel();
+
+        Eigen::VectorXf nsv = Eigen::VectorXf::Zero(cols);
+        for (int i = 0; i < nullspace.cols(); i++)
+        {
+            float squaredNorm = nullspace.col(i).squaredNorm();
+            // Prevent division by zero
+            if (squaredNorm > 1.0e-32f)
+            {
+                nsv += nullspace.col(i) * nullspace.col(i).dot(nullspaceVel) / nullspace.col(i).squaredNorm();
+            }
+        }
+        Eigen::VectorXf jv = invJac * cartesianVel + nsv;
+        LimitInfNormTo(jv, params.maxJointAngleStep);
+
+        Eigen::VectorXf newJointValues = currentJointValues + jv;
+        rns->setJointValues(newJointValues);
+        currentJointValues = newJointValues;
+    }
+
+    bool allReached = true;
+    for (const TargetPtr& target : targets)
+    {
+        allReached = allReached && target->pCtrl.reached(target->target, target->mode, target->maxPosError, target->maxOriError);
+    }
+
+
+    //ARMARX_IMPORTANT << ss.str();
+
+    Result result;
+    result.jointValues = rns->getJointValuesEigen();
+    result.reached = allReached;
+    result.jointLimitMargins = Eigen::VectorXf::Zero(rns->getSize());
+    result.minimumJointLimitMargin = FLT_MAX;
+    for (size_t i = 0; i < rns->getSize(); i++)
+    {
+        VirtualRobot::RobotNodePtr rn = rns->getNode(i);
+        if (rn->isLimitless())
+        {
+            result.jointLimitMargins(i) = M_PI;
+        }
+        else
+        {
+            result.jointLimitMargins(i) = std::min(rn->getJointValue() - rn->getJointLimitLo(), rn->getJointLimitHi() - rn->getJointValue());
+            result.minimumJointLimitMargin = std::min(result.minimumJointLimitMargin, result.jointLimitMargins(i));
+        }
+    }
+
+    return result;
+}
+
+int CompositeDiffIK::CartesianSelectionToSize(VirtualRobot::IKSolver::CartesianSelection mode)
+{
+    switch (mode)
+    {
+        case VirtualRobot::IKSolver::CartesianSelection::Position:
+            return 3;
+        case VirtualRobot::IKSolver::CartesianSelection::Orientation:
+            return 3;
+        case VirtualRobot::IKSolver::CartesianSelection::All:
+            return 6;
+        default:
+            throw LocalException("mode not supported: ") << mode;
+    }
+}
+
+CompositeDiffIK::Target::Target(const VirtualRobot::RobotNodeSetPtr& rns, const VirtualRobot::RobotNodePtr& tcp, const Eigen::Matrix4f& target, VirtualRobot::IKSolver::CartesianSelection mode)
+    : tcp(tcp), target(target), mode(mode), pCtrl(tcp)
+{
+    jacobi = Eigen::MatrixXf::Zero(0, 0);
+}
+
+CompositeDiffIK::NullspaceTarget::NullspaceTarget(const VirtualRobot::RobotNodeSetPtr& rns, const VirtualRobot::RobotNodePtr& tcp, const Eigen::Matrix4f& target, VirtualRobot::IKSolver::CartesianSelection mode)
+    : tcp(tcp), target(target), mode(mode), pCtrl(tcp), vCtrl(rns, tcp)
+{
+
+}
+
+Eigen::VectorXf CompositeDiffIK::NullspaceTarget::getGradient()
+{
+    Eigen::VectorXf cartesianVel = pCtrl.calculate(target, mode);
+    return vCtrl.calculate(cartesianVel, mode);
+}
+
+Eigen::VectorXf CompositeDiffIK::LimitInfNormTo(Eigen::VectorXf vec, float maxValue)
+{
+    float infNorm = vec.lpNorm<Eigen::Infinity>();
+    if (infNorm > maxValue)
+    {
+        vec = vec / infNorm * maxValue;
+    }
+    return vec;
+}
+
+CompositeDiffIK::NullspaceJointTarget::NullspaceJointTarget(const VirtualRobot::RobotNodeSetPtr& rns)
+    : rns(rns), target(rns->getJointValuesEigen()), scale(Eigen::VectorXf::Zero(rns->getSize()))
+{
+
+}
+
+CompositeDiffIK::NullspaceJointTarget::NullspaceJointTarget(const VirtualRobot::RobotNodeSetPtr& rns, const Eigen::VectorXf& target, const Eigen::VectorXf& scale)
+    : rns(rns), target(target), scale(scale)
+{
+
+}
+
+void CompositeDiffIK::NullspaceJointTarget::set(int index, float target, float scale)
+{
+    this->target(index) = target;
+    this->scale(scale) = scale;
+}
+
+Eigen::VectorXf CompositeDiffIK::NullspaceJointTarget::getGradient()
+{
+    return (target - rns->getJointValuesEigen()).cwiseProduct(scale);
+}
+
+CompositeDiffIK::NullspaceJointLimitAvoidance::NullspaceJointLimitAvoidance(const VirtualRobot::RobotNodeSetPtr& rns)
+    : rns(rns), scale(Eigen::VectorXf::Constant(rns->getSize(), 1))
+{
+
+}
+
+CompositeDiffIK::NullspaceJointLimitAvoidance::NullspaceJointLimitAvoidance(const VirtualRobot::RobotNodeSetPtr& rns, const Eigen::VectorXf& scale)
+    : rns(rns), scale(scale)
+{
+
+}
+
+Eigen::VectorXf CompositeDiffIK::NullspaceJointLimitAvoidance::getGradient()
+{
+    Eigen::VectorXf r(rns->getSize());
+    for (size_t i = 0; i < rns->getSize(); i++)
+    {
+        VirtualRobot::RobotNodePtr rn = rns->getNode(i);
+        if (rn->isLimitless())
+        {
+            r(i) = 0;
+        }
+        else
+        {
+            float f = math::Helpers::ILerp(rn->getJointLimitLo(), rn->getJointLimitHi(), rn->getJointValue());
+            r(i) = cos(f * M_PI);
+            //r(i) = math::MathUtils::Lerp(1.f, -1.f, f);
+        }
+    }
+    return r.cwiseProduct(scale);
+}
diff --git a/source/RobotAPI/libraries/diffik/CompositeDiffIK.h b/source/RobotAPI/libraries/diffik/CompositeDiffIK.h
new file mode 100644
index 0000000000000000000000000000000000000000..5da910e5d178ab5bb12a3bf1a52135bc1ceb013a
--- /dev/null
+++ b/source/RobotAPI/libraries/diffik/CompositeDiffIK.h
@@ -0,0 +1,169 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * Copyright (C) 2012-2016, High Performance Humanoid Technologies (H2T),
+ * Karlsruhe Institute of Technology (KIT), all rights reserved.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author     Simon Ottenhaus (simon dot ottenhaus at kit dot edu)
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#pragma once
+#include <VirtualRobot/Nodes/RobotNode.h>
+#include <VirtualRobot/RobotNodeSet.h>
+#include <VirtualRobot/IK/DifferentialIK.h>
+#include <RobotAPI/libraries/core/CartesianPositionController.h>
+#include <RobotAPI/libraries/core/CartesianVelocityController.h>
+
+#include <memory>
+
+
+namespace armarx
+{
+
+
+    typedef std::shared_ptr<class CompositeDiffIK> CompositeDiffIKPtr;
+    class CompositeDiffIK
+    {
+    public:
+        class NullspaceGradient
+        {
+        public:
+            virtual Eigen::VectorXf getGradient() = 0;
+            float kP = 1;
+        };
+        typedef std::shared_ptr<class NullspaceGradient> NullspaceGradientPtr;
+
+        class NullspaceTarget : public NullspaceGradient
+        {
+        public:
+            NullspaceTarget(const VirtualRobot::RobotNodeSetPtr& rns, const VirtualRobot::RobotNodePtr& tcp, const Eigen::Matrix4f& target, VirtualRobot::IKSolver::CartesianSelection mode);
+
+            VirtualRobot::RobotNodePtr tcp;
+            Eigen::Matrix4f target;
+            VirtualRobot::IKSolver::CartesianSelection mode;
+            CartesianPositionController pCtrl;
+            CartesianVelocityController vCtrl;
+
+            Eigen::VectorXf getGradient() override;
+        };
+        typedef std::shared_ptr<class NullspaceTarget> NullspaceTargetPtr;
+
+        class NullspaceJointTarget : public NullspaceGradient
+        {
+        public:
+            NullspaceJointTarget(const VirtualRobot::RobotNodeSetPtr& rns);
+            NullspaceJointTarget(const VirtualRobot::RobotNodeSetPtr& rns, const Eigen::VectorXf& target, const Eigen::VectorXf& scale);
+            void set(int index, float target, float scale);
+
+            VirtualRobot::RobotNodeSetPtr rns;
+            Eigen::VectorXf target;
+            Eigen::VectorXf scale;
+
+            Eigen::VectorXf getGradient() override;
+        };
+        typedef std::shared_ptr<class NullspaceJointTarget> NullspaceJointTargetPtr;
+
+        class NullspaceJointLimitAvoidance : public NullspaceGradient
+        {
+        public:
+            NullspaceJointLimitAvoidance(const VirtualRobot::RobotNodeSetPtr& rns);
+            NullspaceJointLimitAvoidance(const VirtualRobot::RobotNodeSetPtr& rns, const Eigen::VectorXf& scale);
+
+            VirtualRobot::RobotNodeSetPtr rns;
+            Eigen::VectorXf scale;
+
+            Eigen::VectorXf getGradient() override;
+        };
+        typedef std::shared_ptr<class NullspaceJointLimitAvoidance> NullspaceJointLimitAvoidancePtr;
+
+
+        class Target
+        {
+        public:
+            Target(const VirtualRobot::RobotNodeSetPtr& rns, const VirtualRobot::RobotNodePtr& tcp, const Eigen::Matrix4f& target, VirtualRobot::IKSolver::CartesianSelection mode);
+            VirtualRobot::RobotNodePtr tcp;
+            Eigen::Matrix4f target;
+            VirtualRobot::IKSolver::CartesianSelection mode;
+            Eigen::MatrixXf jacobi;
+            CartesianPositionController pCtrl;
+            float maxPosError = 10.f;
+            float maxOriError = 0.05f;
+        };
+        typedef std::shared_ptr<Target> TargetPtr;
+
+        struct Parameters
+        {
+            Parameters() {}
+            // IK params
+            size_t steps = 40;
+
+            float maxJointAngleStep = 0.1f;
+            bool resetRnsValues = true;
+            bool returnIKSteps = false;
+        };
+        struct IKStep
+        {
+            Eigen::VectorXf jointValues;
+            Eigen::Vector3f pdTcp;
+            Eigen::Vector3f odTcp;
+            Eigen::Vector3f pdElb;
+            Eigen::Matrix4f tcpPose;
+            Eigen::Matrix4f elbPose;
+            Eigen::VectorXf cartesianVel;
+            Eigen::VectorXf cartesianVelElb;
+            Eigen::VectorXf jvElb;
+            Eigen::VectorXf jvLA;
+            Eigen::VectorXf jv;
+            Eigen::VectorXf jvClamped;
+        };
+
+        struct Result
+        {
+            Eigen::VectorXf jointValues;
+            Eigen::Vector3f posDiff;
+            Eigen::Vector3f posDiffElbow;
+            Eigen::Vector3f oriDiff;
+            float posError;
+            float posErrorElbow;
+            float oriError;
+            bool reached;
+            Eigen::VectorXf jointLimitMargins;
+            float minimumJointLimitMargin;
+            Eigen::Vector3f elbowPosDiff;
+            std::vector<IKStep> ikSteps;
+        };
+
+        static Eigen::VectorXf LimitInfNormTo(Eigen::VectorXf vec, float maxValue);
+
+        CompositeDiffIK(const VirtualRobot::RobotNodeSetPtr& rns);
+
+        void addTarget(const TargetPtr& target);
+        void addNullspaceGradient(const NullspaceGradientPtr& gradient);
+
+        Result solve(Parameters params);
+
+        int CartesianSelectionToSize(VirtualRobot::IKSolver::CartesianSelection mode);
+
+
+    private:
+        VirtualRobot::RobotNodeSetPtr rns;
+        std::vector<TargetPtr> targets;
+        std::vector<NullspaceGradientPtr> nullspaceGradients;
+        VirtualRobot::DifferentialIKPtr ik;
+
+    };
+}
diff --git a/source/RobotAPI/libraries/diffik/NaturalDiffIK.cpp b/source/RobotAPI/libraries/diffik/NaturalDiffIK.cpp
index f9a6e2a1022e46cae08520a4313a503fc52e022c..4d30bc95d80d65a721bc2394fa8672d1a6c971bb 100644
--- a/source/RobotAPI/libraries/diffik/NaturalDiffIK.cpp
+++ b/source/RobotAPI/libraries/diffik/NaturalDiffIK.cpp
@@ -68,6 +68,9 @@ namespace armarx
 
         //ARMARX_IMPORTANT << "start";
 
+        int posMet = 0;
+        int oriMet = 0;
+
         std::vector<IKStep> ikSteps;
         Eigen::VectorXf currentJointValues = rns->getJointValuesEigen();
         for (size_t i = 0; i <= params.stepsInitial + params.stepsFineTune; i++)
@@ -121,6 +124,29 @@ namespace armarx
             Eigen::VectorXf newJointValues = currentJointValues + jvClamped;
             rns->setJointValues(newJointValues);
             currentJointValues = newJointValues;
+
+            if (pdTcp.norm() > params.maxPosError)
+            {
+                posMet = 0;
+            }
+            else
+            {
+                posMet++;
+            }
+            if (odTcp.norm() > params.maxOriError)
+            {
+                oriMet = 0;
+            }
+            else
+            {
+                oriMet++;
+            }
+
+            // terminate early, if reached for at least 3 iterations
+            if (posMet > 2 && oriMet > 2)
+            {
+                break;
+            }
         }
 
         //ARMARX_IMPORTANT << ss.str();