diff --git a/Source/Constraint/PbdConstraints/imstkPbdConstraint.h b/Source/Constraint/PbdConstraints/imstkPbdConstraint.h
index 419d13e9f8deae582803396707f3fb9f82a9c20f..0b54f9c1c7e83be85f54d2d7b1fdc83f63fe931e 100644
--- a/Source/Constraint/PbdConstraints/imstkPbdConstraint.h
+++ b/Source/Constraint/PbdConstraints/imstkPbdConstraint.h
@@ -133,6 +133,4 @@ protected:
 
     std::vector<Vec3d> m_dcdx;
 };
-
-using PBDConstraintVector = std::vector<std::shared_ptr<PbdConstraint>>;
-}
+}
\ No newline at end of file
diff --git a/Source/Constraint/PbdConstraints/imstkPbdConstraintContainer.cpp b/Source/Constraint/PbdConstraints/imstkPbdConstraintContainer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..218071e7920c414bfcbab6c554fb1c3316d30992
--- /dev/null
+++ b/Source/Constraint/PbdConstraints/imstkPbdConstraintContainer.cpp
@@ -0,0 +1,183 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkPbdConstraintContainer.h"
+#include "imstkGraph.h"
+
+#include <unordered_map>
+
+namespace imstk
+{
+void
+PbdConstraintContainer::addConstraint(std::shared_ptr<PbdConstraint> constraint)
+{
+    m_constraintLock.lock();
+    m_constraints.push_back(constraint);
+    m_constraintLock.unlock();
+}
+
+void
+PbdConstraintContainer::removeConstraint(std::shared_ptr<PbdConstraint> constraint)
+{
+    m_constraintLock.lock();
+    iterator i = std::find(m_constraints.begin(), m_constraints.end(), constraint);
+    if (i != m_constraints.end())
+    {
+        m_constraints.erase(i);
+    }
+    m_constraintLock.unlock();
+}
+
+void
+PbdConstraintContainer::removeConstraintVertices(std::shared_ptr<std::unordered_set<size_t>> vertices)
+{
+    // Remove constraints that contain the given vertices
+    auto removeConstraintFunc = [&](std::shared_ptr<PbdConstraint> constraint)
+                                {
+                                    for (auto i : constraint->getVertexIds())
+                                    {
+                                        if (vertices->find(i) != vertices->end())
+                                        {
+                                            return true;
+                                        }
+                                    }
+                                    return false;
+                                };
+
+    m_constraintLock.lock();
+    m_constraints.erase(std::remove_if(m_constraints.begin(), m_constraints.end(), removeConstraintFunc),
+        m_constraints.end());
+
+    // Also remove partitioned constraints
+    for (auto& pc : m_partitionedConstraints)
+    {
+        pc.erase(std::remove_if(pc.begin(), pc.end(), removeConstraintFunc), pc.end());
+    }
+
+    m_constraintLock.unlock();
+}
+
+void
+PbdConstraintContainer::eraseConstraint(iterator iter)
+{
+    m_constraintLock.lock();
+    m_constraints.erase(iter);
+    m_constraintLock.unlock();
+}
+
+void
+PbdConstraintContainer::partitionConstraints(const int partitionedThreshold)
+{
+    // Form the map { vertex : list_of_constraints_involve_vertex }
+    std::vector<std::shared_ptr<PbdConstraint>>& allConstraints = m_constraints;
+
+    //std::cout << "---------partitionConstraints: " << allConstraints.size() << std::endl;
+
+    std::unordered_map<size_t, std::vector<size_t>> vertexConstraints;
+    for (size_t constrIdx = 0; constrIdx < allConstraints.size(); ++constrIdx)
+    {
+        const auto& constr = allConstraints[constrIdx];
+        for (const auto& vIds : constr->getVertexIds())
+        {
+            vertexConstraints[vIds].push_back(constrIdx);
+        }
+    }
+
+    // Add edges to the constraint graph
+    // Each edge represent a shared vertex between two constraints
+    Graph constraintGraph(allConstraints.size());
+    for (const auto& kv : vertexConstraints)
+    {
+        const auto& constraints = kv.second;     // the list of constraints for a vertex
+        for (size_t i = 0; i < constraints.size(); ++i)
+        {
+            for (size_t j = i + 1; j < constraints.size(); ++j)
+            {
+                constraintGraph.addEdge(constraints[i], constraints[j]);
+            }
+        }
+    }
+    vertexConstraints.clear();
+
+    // do graph coloring for the constraint graph
+    const auto coloring = constraintGraph.doColoring(Graph::ColoringMethod::WelshPowell);
+    // const auto  coloring = constraintGraph.doColoring(Graph::ColoringMethod::Greedy);
+    const auto& partitionIndices = coloring.first;
+    const auto  numPartitions    = coloring.second;
+    assert(partitionIndices.size() == allConstraints.size());
+
+    std::vector<std::vector<std::shared_ptr<PbdConstraint>>>& partitionedConstraints = m_partitionedConstraints;
+    partitionedConstraints.resize(0);
+    partitionedConstraints.resize(static_cast<size_t>(numPartitions));
+
+    for (size_t constrIdx = 0; constrIdx < partitionIndices.size(); ++constrIdx)
+    {
+        const auto partitionIdx = partitionIndices[constrIdx];
+        partitionedConstraints[partitionIdx].push_back(allConstraints[constrIdx]);
+    }
+
+    // If a partition has size smaller than the partition threshold, then move its constraints back
+    // These constraints will be processed sequentially
+    // Because small size partitions yield bad performance upon running in parallel
+    allConstraints.resize(0);
+    for (const auto& constraints : partitionedConstraints)
+    {
+        if (constraints.size() < partitionedThreshold)
+        {
+            for (size_t constrIdx = 0; constrIdx < constraints.size(); ++constrIdx)
+            {
+                allConstraints.push_back(std::move(constraints[constrIdx]));
+            }
+        }
+    }
+
+    // Remove all empty partitions
+    size_t writeIdx = 0;
+    for (size_t readIdx = 0; readIdx < partitionedConstraints.size(); ++readIdx)
+    {
+        if (partitionedConstraints[readIdx].size() >= partitionedThreshold)
+        {
+            if (readIdx != writeIdx)
+            {
+                partitionedConstraints[writeIdx] = std::move(partitionedConstraints[readIdx]);
+            }
+            ++writeIdx;
+        }
+    }
+    partitionedConstraints.resize(writeIdx);
+
+    // Print
+    /*if (print)
+    {
+        size_t numConstraints = 0;
+        int    idx = 0;
+        for (const auto& constraints : partitionedConstraints)
+        {
+            std::cout << "Partition # " << idx++ << " | # nodes: " << constraints.size() << std::endl;
+            numConstraints += constraints.size();
+        }
+        std::cout << "Sequential processing # nodes: " << allConstraints.size() << std::endl;
+        numConstraints += allConstraints.size();
+        std::cout << "Total constraints: " << numConstraints << " | Graph size: "
+            << constraintGraph.size() << std::endl;
+    }*/
+}
+}
\ No newline at end of file
diff --git a/Source/Constraint/PbdConstraints/imstkPbdConstraintContainer.h b/Source/Constraint/PbdConstraints/imstkPbdConstraintContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..69e0165ccd88a4a51b512a5caca1fa312aef84b2
--- /dev/null
+++ b/Source/Constraint/PbdConstraints/imstkPbdConstraintContainer.h
@@ -0,0 +1,103 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkPbdConstraint.h"
+
+#include <unordered_set>
+
+namespace imstk
+{
+///
+/// \class PbdConstraintContainer
+///
+/// \brief Container for pbd constraints
+///
+class PbdConstraintContainer
+{
+public:
+    PbdConstraintContainer() = default;
+    virtual ~PbdConstraintContainer() = default;
+
+public:
+    using iterator = std::vector<std::shared_ptr<PbdConstraint>>::iterator;
+
+public:
+    ///
+    /// \brief Adds a constraint to the system, thread safe
+    ///
+    virtual void addConstraint(std::shared_ptr<PbdConstraint> constraint);
+
+    ///
+    /// \brief Linear searches for and removes a constraint from the system, thread safe
+    ///
+    virtual void removeConstraint(std::shared_ptr<PbdConstraint> constraint);
+
+    ///
+    /// \brief Removes all constraints associated with vertex ids
+    ///
+    virtual void removeConstraintVertices(std::shared_ptr<std::unordered_set<size_t>> vertices);
+
+    ///
+    /// \brief Removes a constraint from the system by iterator, thread safe
+    ///
+    virtual void eraseConstraint(iterator iter);
+
+    ///
+    /// \brief Reserve an amount of constraints in the pool, if you know
+    /// ahead of time the number of constraints, or even an estimate, it
+    /// can be faster to first reserve them
+    ///
+    virtual void reserve(const size_t n) { m_constraints.reserve(n); }
+
+    ///
+    /// \brief Returns if there are no constraints
+    ///
+    const bool empty() const { return m_constraints.empty() && m_partitionedConstraints.empty(); }
+
+    ///
+    /// \brief Get the underlying container
+    ///
+    const std::vector<std::shared_ptr<PbdConstraint>>& getConstraints() const { return m_constraints; }
+
+    ///
+    /// \brief Get the partitioned constraints
+    ///
+    const std::vector<std::vector<std::shared_ptr<PbdConstraint>>> getPartitionedConstraints() const { return m_partitionedConstraints; }
+
+    ///
+    /// \brief Partitions pbd constraints into separate vectors via graph coloring
+    /// \param Minimum number of constraints in groups, any under will be dumped back into m_constraints
+    ///
+    void partitionConstraints(const int partitionThreshold);
+
+    ///
+    /// \brief Clear the parition vectors
+    ///
+    void clearPartitions() { m_partitionedConstraints.clear(); }
+
+protected:
+    std::vector<std::shared_ptr<PbdConstraint>> m_constraints;                         ///> Not partitioned constraints
+    std::vector<std::vector<std::shared_ptr<PbdConstraint>>> m_partitionedConstraints; ///> Partitioned pbd constraints
+    ParallelUtils::SpinLock m_constraintLock;                                          ///> Used to deal with concurrent addition/removal of constraints
+};
+}
\ No newline at end of file
diff --git a/Source/Constraint/PbdConstraints/imstkPbdVolumeConstraint.cpp b/Source/Constraint/PbdConstraints/imstkPbdVolumeConstraint.cpp
index b20802aacc8b05e8c31b942c8ba6ddd0c15ed91d..c1cc32169b8db46ca8f6e8878148a289a1656251 100644
--- a/Source/Constraint/PbdConstraints/imstkPbdVolumeConstraint.cpp
+++ b/Source/Constraint/PbdConstraints/imstkPbdVolumeConstraint.cpp
@@ -21,7 +21,7 @@
 
 #include "imstkPbdVolumeConstraint.h"
 
-namespace  imstk
+namespace imstk
 {
 void
 PbdVolumeConstraint::initConstraint(const VecDataArray<double, 3>& initVertexPositions,
@@ -68,7 +68,8 @@ PbdVolumeConstraint::computeValueAndGradient(
     dcdx[2] = onesixth * (x3 - x0).cross(x1 - x0);
     dcdx[3] = onesixth * (x1 - x0).cross(x2 - x0);
 
-    c = dcdx[3].dot(x3 - x0);
+    const double volume = dcdx[3].dot(x3 - x0);
+    c = (volume - m_restVolume);
     return true;
 }
 } // imstk
diff --git a/Source/DynamicalModels/ObjectModels/imstkPbdConstraintFunctor.h b/Source/DynamicalModels/ObjectModels/imstkPbdConstraintFunctor.h
new file mode 100644
index 0000000000000000000000000000000000000000..8733b8e4002d19e3d615bfdd58cf012b259964f3
--- /dev/null
+++ b/Source/DynamicalModels/ObjectModels/imstkPbdConstraintFunctor.h
@@ -0,0 +1,419 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkLineMesh.h"
+#include "imstkLogger.h"
+#include "imstkParallelUtils.h"
+#include "imstkPbdAreaConstraint.h"
+#include "imstkPbdBendConstraint.h"
+#include "imstkPbdConstantDensityConstraint.h"
+#include "imstkPbdConstraint.h"
+#include "imstkPbdDihedralConstraint.h"
+#include "imstkPbdDistanceConstraint.h"
+#include "imstkPbdFEMConstraint.h"
+#include "imstkPbdFETetConstraint.h"
+#include "imstkPbdVolumeConstraint.h"
+#include "imstkPointSet.h"
+#include "imstkSurfaceMesh.h"
+#include "imstkTetrahedralMesh.h"
+#include "imstkPbdConstraintContainer.h"
+
+namespace imstk
+{
+///
+/// \brief PbdConstraintFunctor take input geometry and produce constraints
+///
+struct PbdConstraintFunctor
+{
+    public:
+        PbdConstraintFunctor() = default;
+        virtual ~PbdConstraintFunctor() = default;
+
+        ///
+        /// \brief Appends a set of constraint to the container given a geometry
+        ///
+        virtual void operator()(PbdConstraintContainer& constraints) = 0;
+
+        void setGeometry(std::shared_ptr<PointSet> geom) { m_geom = geom; }
+
+    public:
+        std::shared_ptr<PointSet> m_geom;
+};
+
+struct PbdDistanceConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdDistanceConstraintFunctor() = default;
+        ~PbdDistanceConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions();
+            auto                           addDistConstraint =
+                [&](std::vector<std::vector<bool>>& E, size_t i1, size_t i2)
+                {
+                    if (i1 > i2) // Make sure i1 is always smaller than i2
+                    {
+                        std::swap(i1, i2);
+                    }
+                    if (E[i1][i2])
+                    {
+                        E[i1][i2] = 0;
+                        auto c = std::make_shared<PbdDistanceConstraint>();
+                        c->initConstraint(vertices, i1, i2, m_stiffness);
+                        constraints.addConstraint(c);
+                    }
+                };
+
+            if (m_geom->getTypeName() == "TetrahedralMesh")
+            {
+                const auto&                    tetMesh  = std::dynamic_pointer_cast<TetrahedralMesh>(m_geom);
+                const VecDataArray<int, 4>&    elements = *tetMesh->getTetrahedraIndices();
+                const auto                     nV       = tetMesh->getNumVertices();
+                std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
+
+                for (int k = 0; k < elements.size(); ++k)
+                {
+                    auto& tet = elements[k];
+                    addDistConstraint(E, tet[0], tet[1]);
+                    addDistConstraint(E, tet[0], tet[2]);
+                    addDistConstraint(E, tet[0], tet[3]);
+                    addDistConstraint(E, tet[1], tet[2]);
+                    addDistConstraint(E, tet[1], tet[3]);
+                    addDistConstraint(E, tet[2], tet[3]);
+                }
+            }
+            else if (m_geom->getTypeName() == "SurfaceMesh")
+            {
+                const auto&                    triMesh  = std::dynamic_pointer_cast<SurfaceMesh>(m_geom);
+                const VecDataArray<int, 3>&    elements = *triMesh->getTriangleIndices();
+                const auto                     nV       = triMesh->getNumVertices();
+                std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
+
+                for (int k = 0; k < elements.size(); ++k)
+                {
+                    auto& tri = elements[k];
+                    addDistConstraint(E, tri[0], tri[1]);
+                    addDistConstraint(E, tri[0], tri[2]);
+                    addDistConstraint(E, tri[1], tri[2]);
+                }
+            }
+            else if (m_geom->getTypeName() == "LineMesh")
+            {
+                const auto&                    lineMesh = std::dynamic_pointer_cast<LineMesh>(m_geom);
+                const VecDataArray<int, 2>&    elements = *lineMesh->getLinesIndices();
+                const auto&                    nV       = lineMesh->getNumVertices();
+                std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
+
+                for (int k = 0; k < elements.size(); k++)
+                {
+                    auto& seg = elements[k];
+                    addDistConstraint(E, seg[0], seg[1]);
+                }
+            }
+        }
+
+        void setStiffness(double stiffness) { m_stiffness = stiffness; }
+
+    protected:
+        double m_stiffness;
+};
+
+struct PbdFemConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdFemConstraintFunctor() = default;
+        ~PbdFemConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            // Check if constraint type matches the mesh type
+            CHECK(m_geom->getTypeName() == "TetrahedralMesh")
+                << "FEM Tetrahedral constraint should come with tetrahedral mesh";
+
+            // Create constraints
+            auto                           tetMesh  = std::dynamic_pointer_cast<TetrahedralMesh>(m_geom);
+            const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions();
+            const VecDataArray<int, 4>&    elements = *tetMesh->getTetrahedraIndices();
+
+            ParallelUtils::parallelFor(elements.size(),
+                [&](const size_t k)
+            {
+                const Vec4i& tet = elements[k];
+                auto c = std::make_shared<PbdFEMTetConstraint>(m_matType);
+                c->initConstraint(vertices,
+                    tet[0], tet[1], tet[2], tet[3], m_femConfig);
+                constraints.addConstraint(c);
+            }, elements.size() > 100);
+        }
+
+        void setMaterialType(PbdFEMTetConstraint::MaterialType materialType) { m_matType = materialType; }
+        void setFemConfig(std::shared_ptr<PbdFEMConstraintConfig> femConfig) { m_femConfig = femConfig; }
+
+    protected:
+        PbdFEMTetConstraint::MaterialType m_matType;
+        std::shared_ptr<PbdFEMConstraintConfig> m_femConfig;
+};
+
+struct PbdVolumeConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdVolumeConstraintFunctor() = default;
+        ~PbdVolumeConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            // Check if constraint type matches the mesh type
+            CHECK(m_geom->getTypeName() == "TetrahedralMesh")
+                << "Volume constraint should come with volumetric mesh";
+
+            // Create constraints
+            auto                           tetMesh  = std::dynamic_pointer_cast<TetrahedralMesh>(m_geom);
+            const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions();
+            const VecDataArray<int, 4>&    elements = *tetMesh->getTetrahedraIndices();
+
+            ParallelUtils::parallelFor(elements.size(),
+                [&](const size_t k)
+            {
+                auto& tet = elements[k];
+                auto c    = std::make_shared<PbdVolumeConstraint>();
+                c->initConstraint(vertices,
+                    tet[0], tet[1], tet[2], tet[3], m_stiffness);
+                constraints.addConstraint(c);
+            });
+        }
+
+        void setStiffness(double stiffness) { m_stiffness = stiffness; }
+
+    protected:
+        double m_stiffness;
+};
+
+struct PbdAreaConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdAreaConstraintFunctor() = default;
+        ~PbdAreaConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            // check if constraint type matches the mesh type
+            CHECK(m_geom->getTypeName() == "SurfaceMesh")
+                << "Area constraint should come with a triangular mesh";
+
+            // ok, now create constraints
+            auto                           triMesh  = std::dynamic_pointer_cast<SurfaceMesh>(m_geom);
+            const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions();
+            const VecDataArray<int, 3>&    elements = *triMesh->getTriangleIndices();
+
+            ParallelUtils::parallelFor(elements.size(),
+                [&](const size_t k)
+            {
+                auto& tri = elements[k];
+                auto c    = std::make_shared<PbdAreaConstraint>();
+                c->initConstraint(vertices, tri[0], tri[1], tri[2], m_stiffness);
+                constraints.addConstraint(c);
+            });
+        }
+
+        void setStiffness(double stiffness) { m_stiffness = stiffness; }
+
+    protected:
+        double m_stiffness;
+};
+
+struct PbdBendConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdBendConstraintFunctor() = default;
+        ~PbdBendConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            CHECK(m_geom->getTypeName() == "LineMesh")
+                << "Bend constraint should come with a line mesh";
+
+            auto                           lineMesh = std::dynamic_pointer_cast<LineMesh>(m_geom);
+            const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions();
+            const VecDataArray<int, 2>&    elements = *lineMesh->getLinesIndices();
+
+            auto addBendConstraint =
+                [&](const double k, size_t i1, size_t i2, size_t i3)
+                {
+                    // i1 should always come first
+                    if (i2 < i1)
+                    {
+                        std::swap(i1, i2);
+                    }
+                    // i3 should always come last
+                    if (i2 > i3)
+                    {
+                        std::swap(i2, i3);
+                    }
+
+                    auto c = std::make_shared<PbdBendConstraint>();
+                    c->initConstraint(vertices, i1, i2, i3, k);
+                    constraints.addConstraint(c);
+                };
+
+            // Iterate sets of two segments
+            for (int k = 0; k < elements.size() - 1; k++)
+            {
+                auto& seg1 = elements[k];
+                auto& seg2 = elements[k + 1];
+                int   i3   = seg2[0];
+                if (i3 == seg1[0] || i3 == seg1[1])
+                {
+                    i3 = seg2[1];
+                }
+                addBendConstraint(m_stiffness, seg1[0], seg1[1], i3);
+            }
+        }
+
+        void setStiffness(double stiffness) { m_stiffness = stiffness; }
+
+    protected:
+        double m_stiffness;
+};
+
+struct PbdDihedralConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdDihedralConstraintFunctor() = default;
+        ~PbdDihedralConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            CHECK(m_geom->getTypeName() == "SurfaceMesh")
+                << "Dihedral constraint should come with a triangular mesh";
+
+            // Create constraints
+            auto                           triMesh  = std::dynamic_pointer_cast<SurfaceMesh>(m_geom);
+            const VecDataArray<double, 3>& vertices = *triMesh->getVertexPositions();
+            const VecDataArray<int, 3>&    elements = *triMesh->getTriangleIndices();
+            const int                      nV       = triMesh->getNumVertices();
+            std::vector<std::vector<int>>  vertIdsToTriangleIds(nV);
+
+            for (int k = 0; k < elements.size(); ++k)
+            {
+                const Vec3i& tri = elements[k];
+                vertIdsToTriangleIds[tri[0]].push_back(k);
+                vertIdsToTriangleIds[tri[1]].push_back(k);
+                vertIdsToTriangleIds[tri[2]].push_back(k);
+            }
+
+            // Used to resolve duplicates
+            std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
+
+            auto addDihedralConstraint =
+                [&](const std::vector<int>& r1, const std::vector<int>& r2,
+                    const int k, int i1, int i2)
+                {
+                    if (i1 > i2) // Make sure i1 is always smaller than i2
+                    {
+                        std::swap(i1, i2);
+                    }
+                    if (E[i1][i2])
+                    {
+                        E[i1][i2] = 0;
+
+                        // Find the shared edge
+                        std::vector<size_t> rs(2);
+                        auto                it = std::set_intersection(r1.begin(), r1.end(), r2.begin(), r2.end(), rs.begin());
+                        rs.resize(static_cast<size_t>(it - rs.begin()));
+                        if (rs.size() > 1)
+                        {
+                            size_t      idx  = (rs[0] == k) ? 1 : 0;
+                            const auto& tri0 = elements[k];
+                            const auto& tri1 = elements[rs[idx]];
+                            size_t      idx0 = 0;
+                            size_t      idx1 = 0;
+                            for (size_t i = 0; i < 3; ++i)
+                            {
+                                if (tri0[i] != i1 && tri0[i] != i2)
+                                {
+                                    idx0 = tri0[i];
+                                }
+                                if (tri1[i] != i1 && tri1[i] != i2)
+                                {
+                                    idx1 = tri1[i];
+                                }
+                            }
+                            auto c = std::make_shared<PbdDihedralConstraint>();
+                            c->initConstraint(vertices, idx0, idx1, i1, i2, m_stiffness);
+                            constraints.addConstraint(c);
+                        }
+                    }
+                };
+
+            for (int i = 0; i < vertIdsToTriangleIds.size(); i++)
+            {
+                std::sort(vertIdsToTriangleIds[i].begin(), vertIdsToTriangleIds[i].end());
+            }
+
+            // For every triangle
+            for (int k = 0; k < elements.size(); ++k)
+            {
+                const Vec3i& tri = elements[k];
+
+                // Get all the neighbor triangles (to the vertices)
+                std::vector<int>& neighborTriangles0 = vertIdsToTriangleIds[tri[0]];
+                std::vector<int>& neighborTriangles1 = vertIdsToTriangleIds[tri[1]];
+                std::vector<int>& neighborTriangles2 = vertIdsToTriangleIds[tri[2]];
+
+                // Add constraints between all the triangles
+                addDihedralConstraint(neighborTriangles0, neighborTriangles1, k, tri[0], tri[1]);
+                addDihedralConstraint(neighborTriangles0, neighborTriangles2, k, tri[0], tri[2]);
+                addDihedralConstraint(neighborTriangles1, neighborTriangles2, k, tri[1], tri[2]);
+            }
+        }
+
+        void setStiffness(double stiffness) { m_stiffness = stiffness; }
+
+    protected:
+        double m_stiffness;
+};
+
+struct PbdConstantDensityConstraintFunctor : public PbdConstraintFunctor
+{
+    public:
+        PbdConstantDensityConstraintFunctor() = default;
+        ~PbdConstantDensityConstraintFunctor() override = default;
+
+        virtual void operator()(PbdConstraintContainer& constraints) override
+        {
+            // check if constraint type matches the mesh type
+            CHECK(std::dynamic_pointer_cast<PointSet>(m_geom) != nullptr)
+                << "Constant constraint should come with a mesh!";
+
+            auto c = std::make_shared<PbdConstantDensityConstraint>();
+            c->initConstraint(*m_geom->getVertexPositions(), m_stiffness);
+            constraints.addConstraint(c);
+        }
+
+        void setStiffness(double stiffness) { m_stiffness = stiffness; }
+
+    protected:
+        double m_stiffness;
+};
+}
\ No newline at end of file
diff --git a/Source/DynamicalModels/ObjectModels/imstkPbdModel.cpp b/Source/DynamicalModels/ObjectModels/imstkPbdModel.cpp
index 8986b73e01551b01ee4a133171a285f2ac547160..56a6f09debaace5c5e01cf3eef52d9b377e6ad34 100644
--- a/Source/DynamicalModels/ObjectModels/imstkPbdModel.cpp
+++ b/Source/DynamicalModels/ObjectModels/imstkPbdModel.cpp
@@ -24,17 +24,12 @@
 #include "imstkLineMesh.h"
 #include "imstkLogger.h"
 #include "imstkParallelUtils.h"
-#include "imstkPbdAreaConstraint.h"
-#include "imstkPbdBendConstraint.h"
-#include "imstkPbdConstantDensityConstraint.h"
-#include "imstkPbdDihedralConstraint.h"
-#include "imstkPbdDistanceConstraint.h"
 #include "imstkPbdFETetConstraint.h"
 #include "imstkPbdSolver.h"
-#include "imstkPbdVolumeConstraint.h"
 #include "imstkSurfaceMesh.h"
 #include "imstkTaskGraph.h"
 #include "imstkTetrahedralMesh.h"
+#include "imstkPbdConstraintFunctor.h"
 
 #include <map>
 #include <set>
@@ -110,74 +105,73 @@ PbdModel::initialize()
 
     // Initialize constraints
     {
-        m_constraints = std::make_shared<PBDConstraintVector>();
-        m_partitionedConstraints = std::make_shared<std::vector<PBDConstraintVector>>();
+        m_constraints = std::make_shared<PbdConstraintContainer>();
 
-        // Initialize FEM constraints
-        for (auto& constraint : m_parameters->m_FEMConstraints)
+        computeElasticConstants();
+
+        auto pointSet = std::dynamic_pointer_cast<PointSet>(getModelGeometry());
+        for (auto constraintType : m_parameters->m_FEMConstraints)
         {
-            computeElasticConstants();
-            if (!initializeFEMConstraints(constraint.second))
-            {
-                return false;
-            }
+            auto functor = std::make_shared<PbdFemConstraintFunctor>();
+            functor->setFemConfig(m_parameters->m_femParams);
+            functor->setMaterialType(constraintType.second);
+            m_functors.push_back(functor);
         }
-
-        // Initialize other constraints
-        for (auto& constraint : m_parameters->m_regularConstraints)
+        for (auto constraintType : m_parameters->m_regularConstraints)
         {
-            if (m_parameters->m_solverType == PbdConstraint::SolverType::PBD && constraint.second > 1.0)
+            if (constraintType.first == PbdConstraint::Type::Area)
             {
-                LOG(WARNING) << "for PBD, k should be between [0, 1]";
+                auto functor = std::make_shared<PbdAreaConstraintFunctor>();
+                functor->setStiffness(constraintType.second);
+                m_functors.push_back(functor);
             }
-            else if (m_parameters->m_solverType == PbdConstraint::SolverType::xPBD && constraint.second <= 1.0)
+            else if (constraintType.first == PbdConstraint::Type::Bend)
             {
-                LOG(WARNING) << "for xPBD, k is Young's Modulu, and should be much larger than 1";
+                auto functor = std::make_shared<PbdBendConstraintFunctor>();
+                functor->setStiffness(constraintType.second);
+                m_functors.push_back(functor);
             }
-
-            if (!bOK)
+            else if (constraintType.first == PbdConstraint::Type::ConstantDensity)
             {
-                return false;
+                auto functor = std::make_shared<PbdConstantDensityConstraintFunctor>();
+                functor->setStiffness(constraintType.second);
+                m_functors.push_back(functor);
             }
-            switch (constraint.first)
+            else if (constraintType.first == PbdConstraint::Type::Dihedral)
             {
-            case PbdConstraint::Type::Volume:
-                bOK = initializeVolumeConstraints(constraint.second);
-                break;
-
-            case PbdConstraint::Type::Distance:
-                bOK = initializeDistanceConstraints(constraint.second);
-                break;
-
-            case PbdConstraint::Type::Area:
-                bOK = initializeAreaConstraints(constraint.second);
-                break;
-
-            case PbdConstraint::Type::Bend:
-                bOK = initializeBendConstraints(constraint.second);
-                break;
-
-            case PbdConstraint::Type::Dihedral:
-                bOK = initializeDihedralConstraints(constraint.second);
-                break;
-
-            case PbdConstraint::Type::ConstantDensity:
-                bOK = initializeConstantDensityConstraint(constraint.second);
-                break;
-
-            default:
-                LOG(FATAL) << "Invalid constraint type";
+                auto functor = std::make_shared<PbdDihedralConstraintFunctor>();
+                functor->setStiffness(constraintType.second);
+                m_functors.push_back(functor);
+            }
+            else if (constraintType.first == PbdConstraint::Type::Distance)
+            {
+                auto functor = std::make_shared<PbdDistanceConstraintFunctor>();
+                functor->setStiffness(constraintType.second);
+                m_functors.push_back(functor);
+            }
+            else if (constraintType.first == PbdConstraint::Type::Volume)
+            {
+                auto functor = std::make_shared<PbdVolumeConstraintFunctor>();
+                functor->setStiffness(constraintType.second);
+                m_functors.push_back(functor);
             }
         }
 
+        for (auto functorPtr : m_functors)
+        {
+            PbdConstraintFunctor& functor = *functorPtr;
+            functor.setGeometry(pointSet);
+            functor(*m_constraints);
+        }
+
         // Partition constraints for parallel computation
         if (m_parameters->m_doPartitioning)
         {
-            this->partitionConstraints();
+            m_constraints->partitionConstraints(m_partitionThreshold);
         }
         else
         {
-            m_partitionedConstraints->clear();
+            m_constraints->clearPartitions();
         }
     }
 
@@ -191,7 +185,6 @@ PbdModel::initialize()
     m_pbdSolver->setPositions(getCurrentState()->getPositions());
     m_pbdSolver->setInvMasses(getInvMasses());
     m_pbdSolver->setConstraints(getConstraints());
-    m_pbdSolver->setPartitionedConstraints(getPartitionedConstraints());
     m_pbdSolver->setTimeStep(m_parameters->m_dt);
 
     this->setTimeStepSizeType(m_timeStepSizeType);
@@ -301,326 +294,11 @@ PbdModel::computeElasticConstants()
     }
 }
 
-bool
-PbdModel::initializeFEMConstraints(PbdFEMConstraint::MaterialType type)
-{
-    // Check if constraint type matches the mesh type
-    CHECK(m_mesh->getTypeName() == "TetrahedralMesh")
-        << "FEM Tetrahedral constraint should come with tetrahedral mesh";
-
-    // Create constraints
-    const auto&                 tetMesh  = std::static_pointer_cast<TetrahedralMesh>(m_mesh);
-    const VecDataArray<int, 4>& elements = *tetMesh->getTetrahedraIndices();
-
-    ParallelUtils::SpinLock lock;
-    ParallelUtils::parallelFor(elements.size(),
-        [&](const size_t k)
-        {
-            const Vec4i& tet = elements[k];
-            auto c = std::make_shared<PbdFEMTetConstraint>(type);
-            c->initConstraint(*m_initialState->getPositions(),
-                tet[0], tet[1], tet[2], tet[3], m_parameters->m_femParams);
-            lock.lock();
-            m_constraints->push_back(std::move(c));
-            lock.unlock();
-        }, elements.size() > 100);
-    return true;
-}
-
-bool
-PbdModel::initializeVolumeConstraints(const double stiffness)
-{
-    // Check if constraint type matches the mesh type
-    CHECK(m_mesh->getTypeName() == "TetrahedralMesh")
-        << "Volume constraint should come with volumetric mesh";
-
-    // Create constraints
-    const auto&                 tetMesh  = std::static_pointer_cast<TetrahedralMesh>(m_mesh);
-    const VecDataArray<int, 4>& elements = *tetMesh->getTetrahedraIndices();
-
-    ParallelUtils::SpinLock lock;
-    ParallelUtils::parallelFor(elements.size(),
-        [&](const size_t k)
-        {
-            auto& tet = elements[k];
-            auto c    = std::make_shared<PbdVolumeConstraint>();
-            c->initConstraint(*m_initialState->getPositions(),
-                tet[0], tet[1], tet[2], tet[3], stiffness);
-            lock.lock();
-            m_constraints->push_back(std::move(c));
-            lock.unlock();
-        });
-    return true;
-}
-
-bool
-PbdModel::initializeDistanceConstraints(const double stiffness)
-{
-    auto addConstraint =
-        [&](std::vector<std::vector<bool>>& E, size_t i1, size_t i2)
-        {
-            if (i1 > i2)     // Make sure i1 is always smaller than i2
-            {
-                std::swap(i1, i2);
-            }
-            if (E[i1][i2])
-            {
-                E[i1][i2] = 0;
-                auto c = std::make_shared<PbdDistanceConstraint>();
-                c->initConstraint(*m_initialState->getPositions(), i1, i2, stiffness);
-                m_constraints->push_back(std::move(c));
-            }
-        };
-
-    if (m_mesh->getTypeName() == "TetrahedralMesh")
-    {
-        const auto&                    tetMesh  = std::static_pointer_cast<TetrahedralMesh>(m_mesh);
-        const VecDataArray<int, 4>&    elements = *tetMesh->getTetrahedraIndices();
-        const auto                     nV       = tetMesh->getNumVertices();
-        std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
-
-        for (int k = 0; k < elements.size(); ++k)
-        {
-            auto& tet = elements[k];
-            addConstraint(E, tet[0], tet[1]);
-            addConstraint(E, tet[0], tet[2]);
-            addConstraint(E, tet[0], tet[3]);
-            addConstraint(E, tet[1], tet[2]);
-            addConstraint(E, tet[1], tet[3]);
-            addConstraint(E, tet[2], tet[3]);
-        }
-    }
-    else if (m_mesh->getTypeName() == "SurfaceMesh")
-    {
-        const auto&                    triMesh  = std::static_pointer_cast<SurfaceMesh>(m_mesh);
-        const VecDataArray<int, 3>&    elements = *triMesh->getTriangleIndices();
-        const auto                     nV       = triMesh->getNumVertices();
-        std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
-
-        for (int k = 0; k < elements.size(); ++k)
-        {
-            auto& tri = elements[k];
-            addConstraint(E, tri[0], tri[1]);
-            addConstraint(E, tri[0], tri[2]);
-            addConstraint(E, tri[1], tri[2]);
-        }
-    }
-    else if (m_mesh->getTypeName() == "LineMesh")
-    {
-        const auto&                    lineMesh = std::static_pointer_cast<LineMesh>(m_mesh);
-        const VecDataArray<int, 2>&    elements = *lineMesh->getLinesIndices();
-        const auto&                    nV       = lineMesh->getNumVertices();
-        std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
-
-        for (int k = 0; k < elements.size(); k++)
-        {
-            auto& seg = elements[k];
-            addConstraint(E, seg[0], seg[1]);
-        }
-    }
-
-    return true;
-}
-
-bool
-PbdModel::initializeAreaConstraints(const double stiffness)
-{
-    // check if constraint type matches the mesh type
-    CHECK(m_mesh->getTypeName() == "SurfaceMesh")
-        << "Area constraint should come with a triangular mesh";
-
-    // ok, now create constraints
-    const auto&                 triMesh  = std::static_pointer_cast<SurfaceMesh>(m_mesh);
-    const VecDataArray<int, 3>& elements = *triMesh->getTriangleIndices();
-
-    ParallelUtils::SpinLock lock;
-    ParallelUtils::parallelFor(elements.size(),
-        [&](const size_t k)
-        {
-            auto& tri = elements[k];
-            auto c    = std::make_shared<PbdAreaConstraint>();
-            c->initConstraint(*m_initialState->getPositions(), tri[0], tri[1], tri[2], stiffness);
-            lock.lock();
-            m_constraints->push_back(std::move(c));
-            lock.unlock();
-        });
-    return true;
-}
-
-bool
-PbdModel::initializeBendConstraints(const double stiffness)
-{
-    CHECK(m_mesh->getTypeName() == "LineMesh")
-        << "Bend constraint should come with a line mesh";
-
-    auto addConstraint =
-        [&](const double k, size_t i1, size_t i2, size_t i3)
-        {
-            // i1 should always come first
-            if (i2 < i1)
-            {
-                std::swap(i1, i2);
-            }
-            // i3 should always come last
-            if (i2 > i3)
-            {
-                std::swap(i2, i3);
-            }
-
-            auto c = std::make_shared<PbdBendConstraint>();
-            c->initConstraint(*m_initialState->getPositions(), i1, i2, i3, k);
-            m_constraints->push_back(std::move(c));
-        };
-
-    // Create constraints
-    const auto&                 lineMesh = std::static_pointer_cast<LineMesh>(m_mesh);
-    const VecDataArray<int, 2>& elements = *lineMesh->getLinesIndices();
-
-    // Iterate sets of two segments
-    for (int k = 0; k < elements.size() - 1; k++)
-    {
-        auto& seg1 = elements[k];
-        auto& seg2 = elements[k + 1];
-        int   i3   = seg2[0];
-        if (i3 == seg1[0] || i3 == seg1[1])
-        {
-            i3 = seg2[1];
-        }
-        addConstraint(stiffness, seg1[0], seg1[1], i3);
-    }
-    return true;
-}
-
-bool
-PbdModel::initializeDihedralConstraints(const double stiffness)
-{
-    CHECK(m_mesh->getTypeName() == "SurfaceMesh")
-        << "Dihedral constraint should come with a triangular mesh";
-
-    // Create constraints
-    const auto&                   triMesh  = std::static_pointer_cast<SurfaceMesh>(m_mesh);
-    const VecDataArray<int, 3>&   elements = *triMesh->getTriangleIndices();
-    const auto                    nV       = triMesh->getNumVertices();
-    std::vector<std::vector<int>> vertIdsToTriangleIds(nV);
-
-    for (int k = 0; k < elements.size(); ++k)
-    {
-        const Vec3i& tri = elements[k];
-        vertIdsToTriangleIds[tri[0]].push_back(k);
-        vertIdsToTriangleIds[tri[1]].push_back(k);
-        vertIdsToTriangleIds[tri[2]].push_back(k);
-    }
-
-    // Used to resolve duplicates
-    std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1));
-
-    auto addConstraint =
-        [&](const std::vector<int>& r1, const std::vector<int>& r2,
-            const int k, int i1, int i2)
-        {
-            if (i1 > i2) // Make sure i1 is always smaller than i2
-            {
-                std::swap(i1, i2);
-            }
-            if (E[i1][i2])
-            {
-                E[i1][i2] = 0;
-
-                // Find the shared edge
-                std::vector<size_t> rs(2);
-                auto                it = std::set_intersection(r1.begin(), r1.end(), r2.begin(), r2.end(), rs.begin());
-                rs.resize(static_cast<size_t>(it - rs.begin()));
-                if (rs.size() > 1)
-                {
-                    size_t      idx  = (rs[0] == k) ? 1 : 0;
-                    const auto& tri0 = elements[k];
-                    const auto& tri1 = elements[rs[idx]];
-                    size_t      idx0 = 0;
-                    size_t      idx1 = 0;
-                    for (size_t i = 0; i < 3; ++i)
-                    {
-                        if (tri0[i] != i1 && tri0[i] != i2)
-                        {
-                            idx0 = tri0[i];
-                        }
-                        if (tri1[i] != i1 && tri1[i] != i2)
-                        {
-                            idx1 = tri1[i];
-                        }
-                    }
-                    auto c = std::make_shared<PbdDihedralConstraint>();
-                    c->initConstraint(*m_initialState->getPositions(), idx0, idx1, i1, i2, stiffness);
-                    m_constraints->push_back(std::move(c));
-                }
-            }
-        };
-
-    for (int i = 0; i < vertIdsToTriangleIds.size(); i++)
-    {
-        std::sort(vertIdsToTriangleIds[i].begin(), vertIdsToTriangleIds[i].end());
-    }
-
-    // For every triangle
-    for (int k = 0; k < elements.size(); ++k)
-    {
-        const Vec3i& tri = elements[k];
-
-        // Get all the neighbor triangles (to the vertices)
-        std::vector<int>& neighborTriangles0 = vertIdsToTriangleIds[tri[0]];
-        std::vector<int>& neighborTriangles1 = vertIdsToTriangleIds[tri[1]];
-        std::vector<int>& neighborTriangles2 = vertIdsToTriangleIds[tri[2]];
-
-        // Add constraints between all the triangles
-        addConstraint(neighborTriangles0, neighborTriangles1, k, tri[0], tri[1]);
-        addConstraint(neighborTriangles0, neighborTriangles2, k, tri[0], tri[2]);
-        addConstraint(neighborTriangles1, neighborTriangles2, k, tri[1], tri[2]);
-    }
-    return true;
-}
-
-bool
-PbdModel::initializeConstantDensityConstraint(const double stiffness)
-{
-    // check if constraint type matches the mesh type
-    CHECK(std::dynamic_pointer_cast<PointSet>(m_mesh) != nullptr)
-        << "Constant constraint should come with a mesh!";
-
-    auto c = std::make_shared<PbdConstantDensityConstraint>();
-    c->initConstraint(*m_initialState->getPositions(), stiffness);
-    m_constraints->push_back(std::move(c));
-
-    return true;
-}
-
-void
-PbdModel::removeConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
-{
-    // constraint removal predicate
-    auto removeConstraint =
-        [&](std::shared_ptr<PbdConstraint> constraint)
-        {
-            for (auto i : constraint->getVertexIds())
-            {
-                if (vertices->find(i) != vertices->end())
-                {
-                    return true;
-                }
-            }
-            return false;
-        };
-
-    // remove constraints from constraint-partition maps and constraint vectors.
-    m_constraints->erase(std::remove_if(m_constraints->begin(), m_constraints->end(), removeConstraint),
-                         m_constraints->end());
-    for (auto& pc : *m_partitionedConstraints)
-    {
-        pc.erase(std::remove_if(pc.begin(), pc.end(), removeConstraint), pc.end());
-    }
-}
-
 void
 PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
 {
+    // \todo: Refactor into functors, then move to ConstrainerContainer
+
     // check if constraint type matches the mesh type
     CHECK(m_mesh->getTypeName() == "SurfaceMesh")
         << "Add element constraints does not support current mesh type.";
@@ -653,7 +331,7 @@ PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
         {
             auto c = std::make_shared<PbdDistanceConstraint>();
             c->initConstraint(*m_initialState->getPositions(), i1, i2, stiffness);
-            m_constraints->push_back(std::move(c));
+            m_constraints->addConstraint(c);
         };
     auto addAreaConstraint =
         [&](size_t k, double stiffness)
@@ -661,7 +339,7 @@ PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
             auto& tri = (*elements)[k];
             auto  c   = std::make_shared<PbdAreaConstraint>();
             c->initConstraint(*m_initialState->getPositions(), tri[0], tri[1], tri[2], stiffness);
-            m_constraints->push_back(std::move(c));
+            m_constraints->addConstraint(c);
         };
     auto addDihedralConstraint =
         [&](size_t t0, size_t t1, size_t i1, size_t i2, double stiffness)
@@ -683,7 +361,7 @@ PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
             }
             auto c = std::make_shared<PbdDihedralConstraint>();
             c->initConstraint(*m_initialState->getPositions(), idx0, idx1, i1, i2, stiffness);
-            m_constraints->push_back(std::move(c));
+            m_constraints->addConstraint(c);
         };
 
     // count constraints to be added for pre-allocation
@@ -761,7 +439,7 @@ PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
     }
 
     // add constraints
-    m_constraints->reserve(m_constraints->size() + distanceSet.size() + areaSet.size() + dihedralSet.size());
+    m_constraints->reserve(m_constraints->getConstraints().size() + distanceSet.size() + areaSet.size() + dihedralSet.size());
     for (auto& constraint : m_parameters->m_regularConstraints)
     {
         switch (constraint.first)
@@ -785,116 +463,6 @@ PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices)
     }
 }
 
-void
-PbdModel::addConstraint(std::shared_ptr<PbdConstraint> constraint)
-{
-    m_constraints->push_back(constraint);
-}
-
-void
-PbdModel::removeConstraint(std::shared_ptr<PbdConstraint> constraint)
-{
-    m_constraints->erase(std::find(m_constraints->begin(), m_constraints->end(), constraint));
-}
-
-void
-PbdModel::partitionConstraints(const bool print)
-{
-    // Form the map { vertex : list_of_constraints_involve_vertex }
-    PBDConstraintVector& allConstraints = *m_constraints;
-
-    //std::cout << "---------partitionConstraints: " << allConstraints.size() << std::endl;
-
-    std::unordered_map<size_t, std::vector<size_t>> vertexConstraints;
-    for (size_t constrIdx = 0; constrIdx < allConstraints.size(); ++constrIdx)
-    {
-        const auto& constr = allConstraints[constrIdx];
-        for (const auto& vIds : constr->getVertexIds())
-        {
-            vertexConstraints[vIds].push_back(constrIdx);
-        }
-    }
-
-    // Add edges to the constraint graph
-    // Each edge represent a shared vertex between two constraints
-    Graph constraintGraph(allConstraints.size());
-    for (const auto& kv : vertexConstraints)
-    {
-        const auto& constraints = kv.second;     // the list of constraints for a vertex
-        for (size_t i = 0; i < constraints.size(); ++i)
-        {
-            for (size_t j = i + 1; j < constraints.size(); ++j)
-            {
-                constraintGraph.addEdge(constraints[i], constraints[j]);
-            }
-        }
-    }
-    vertexConstraints.clear();
-
-    // do graph coloring for the constraint graph
-    const auto coloring = constraintGraph.doColoring(Graph::ColoringMethod::WelshPowell);
-    // const auto  coloring = constraintGraph.doColoring(Graph::ColoringMethod::Greedy);
-    const auto& partitionIndices = coloring.first;
-    const auto  numPartitions    = coloring.second;
-    assert(partitionIndices.size() == allConstraints.size());
-
-    std::vector<PBDConstraintVector>& partitionedConstraints = *m_partitionedConstraints;
-    partitionedConstraints.resize(0);
-    partitionedConstraints.resize(static_cast<size_t>(numPartitions));
-
-    for (size_t constrIdx = 0; constrIdx < partitionIndices.size(); ++constrIdx)
-    {
-        const auto partitionIdx = partitionIndices[constrIdx];
-        partitionedConstraints[partitionIdx].push_back(allConstraints[constrIdx]);
-    }
-
-    // If a partition has size smaller than the partition threshold, then move its constraints back
-    // These constraints will be processed sequentially
-    // Because small size partitions yield bad performance upon running in parallel
-    allConstraints.resize(0);
-    for (const auto& constraints : partitionedConstraints)
-    {
-        if (constraints.size() < m_partitionThreshold)
-        {
-            for (size_t constrIdx = 0; constrIdx < constraints.size(); ++constrIdx)
-            {
-                allConstraints.push_back(std::move(constraints[constrIdx]));
-            }
-        }
-    }
-
-    // Remove all empty partitions
-    size_t writeIdx = 0;
-    for (size_t readIdx = 0; readIdx < partitionedConstraints.size(); ++readIdx)
-    {
-        if (partitionedConstraints[readIdx].size() >= m_partitionThreshold)
-        {
-            if (readIdx != writeIdx)
-            {
-                partitionedConstraints[writeIdx] = std::move(partitionedConstraints[readIdx]);
-            }
-            ++writeIdx;
-        }
-    }
-    partitionedConstraints.resize(writeIdx);
-
-    // Print
-    if (print)
-    {
-        size_t numConstraints = 0;
-        int    idx = 0;
-        for (const auto& constraints : partitionedConstraints)
-        {
-            std::cout << "Partition # " << idx++ << " | # nodes: " << constraints.size() << std::endl;
-            numConstraints += constraints.size();
-        }
-        std::cout << "Sequential processing # nodes: " << allConstraints.size() << std::endl;
-        numConstraints += allConstraints.size();
-        std::cout << "Total constraints: " << numConstraints << " | Graph size: "
-                  << constraintGraph.size() << std::endl;
-    }
-}
-
 void
 PbdModel::setParticleMass(const double val, const size_t idx)
 {
diff --git a/Source/DynamicalModels/ObjectModels/imstkPbdModel.h b/Source/DynamicalModels/ObjectModels/imstkPbdModel.h
index 3098532583a85d27b281c20a7c29e6ad8f114242..d73d67b6806845496ac14f6c1d9813c658ccdbb7 100644
--- a/Source/DynamicalModels/ObjectModels/imstkPbdModel.h
+++ b/Source/DynamicalModels/ObjectModels/imstkPbdModel.h
@@ -31,7 +31,9 @@
 
 namespace imstk
 {
+struct PbdConstraintFunctor;
 class PointSet;
+class PbdConstraintContainer;
 class PbdSolver;
 
 ///
@@ -124,80 +126,20 @@ public:
     ///
     void computeElasticConstants();
 
-    ///
-    /// \brief Initialize FEM constraints
-    ///
-    bool initializeFEMConstraints(PbdFEMConstraint::MaterialType type);
-
-    ///
-    /// \brief Initialize volume constraints
-    ///
-    bool initializeVolumeConstraints(const double stiffness);
-
-    ///
-    /// \brief Initialize distance constraints
-    ///
-    bool initializeDistanceConstraints(const double stiffness);
-
-    ///
-    /// \brief Initialize area constraints
-    ///
-    bool initializeAreaConstraints(const double stiffness);
-
-    ///
-    /// \brief Initialize bend constraints
-    ///
-    bool initializeBendConstraints(const double stiffness);
-
-    ///
-    /// \brief Initialize dihedral constraints
-    ///
-    bool initializeDihedralConstraints(const double stiffness);
-
-    ///
-    /// \brief Initialize constant density constraints for PBD fluid
-    ///
-    bool initializeConstantDensityConstraint(const double stiffness);
-
-    ///
-    /// \brief Returns true if there is at least one constraint
-    ///
-    bool hasConstraints() const { return !m_constraints->empty() || !m_partitionedConstraints->empty(); }
-
-    ///
-    /// \brief Remove constraints related to a set of vertices.
-    ///
-    void removeConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices);
-
     ///
     /// \brief Add constraints related to a set of vertices.
     /// \brief Does not check for duplicating pre-existed constraints.
+    /// \todo: Move to containers and functors
     ///
     void addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices);
 
-    ///
-    /// \brief Adds a constraint to the system, added to the set of sequentially
-    /// solved constraints
-    ///
-    void addConstraint(std::shared_ptr<PbdConstraint> constraint);
-
-    ///
-    /// \brief Searches for and removes a constraint from the system
-    ///
-    void removeConstraint(std::shared_ptr<PbdConstraint> constraint);
-
     virtual void setTimeStep(const double timeStep) override { m_parameters->m_dt = timeStep; }
     double getTimeStep() const override { return m_parameters->m_dt; }
 
     ///
     /// \brief Return all constraints that are solved sequentially
     ///
-    std::shared_ptr<PBDConstraintVector> getConstraints() { return m_constraints; }
-
-    ///
-    /// \brief Return the constraints that are colored and run in parallel
-    ///
-    std::shared_ptr<std::vector<PBDConstraintVector>> getPartitionedConstraints() { return m_partitionedConstraints; }
+    std::shared_ptr<PbdConstraintContainer> getConstraints() { return m_constraints; }
 
     ///
     /// \brief Set mass to particular node
@@ -259,6 +201,11 @@ public:
     ///
     void setSolver(std::shared_ptr<PbdSolver> solver) { this->m_pbdSolver = solver; }
 
+    ///
+    /// \brief Adds a functor to generate constraints
+    ///
+    void addPbdConstraintFunctor(std::shared_ptr<PbdConstraintFunctor> functor) { m_functors.push_back(functor); }
+
     std::shared_ptr<TaskNode> getIntegratePositionNode() const { return m_integrationPositionNode; }
 
     std::shared_ptr<TaskNode> getUpdateCollisionGeometryNode() const { return m_updateCollisionGeometryNode; }
@@ -268,11 +215,6 @@ public:
     std::shared_ptr<TaskNode> getUpdateVelocityNode() const { return m_updateVelocityNode; }
 
 protected:
-    ///
-    /// \brief Partition constraints for parallelization
-    ///
-    void partitionConstraints(const bool print = false);
-
     ///
     /// \brief Setup the computational graph of PBD
     ///
@@ -287,10 +229,12 @@ protected:
     std::shared_ptr<DataArray<double>> m_invMass = nullptr;                               ///> Inverse of mass of nodes
     std::shared_ptr<std::unordered_map<size_t, double>> m_fixedNodeInvMass = nullptr;     ///> Map for archiving fixed nodes' mass.
 
-    std::shared_ptr<PBDConstraintVector> m_constraints = nullptr;                         ///> List of pbd constraints
-    std::shared_ptr<std::vector<PBDConstraintVector>> m_partitionedConstraints = nullptr; ///> List of pbd constraints
     std::shared_ptr<PBDModelConfig> m_parameters = nullptr;                               ///> Model parameters, must be set before simulation
 
+protected:
+    std::shared_ptr<PbdConstraintContainer> m_constraints;         ///> The set of constraints to update/use
+    std::vector<std::shared_ptr<PbdConstraintFunctor>> m_functors; ///> The set of functors for constraint generation
+
 protected:
     // Computational Nodes
     std::shared_ptr<TaskNode> m_integrationPositionNode     = nullptr;
diff --git a/Source/Scene/imstkPbdObjectCuttingPair.cpp b/Source/Scene/imstkPbdObjectCuttingPair.cpp
index 12b04c31f21ccff1856ea857ba604a28a7917dca..04aa449bc31d7eefb7bf0dc880a38ac08c02da8b 100644
--- a/Source/Scene/imstkPbdObjectCuttingPair.cpp
+++ b/Source/Scene/imstkPbdObjectCuttingPair.cpp
@@ -25,6 +25,7 @@ limitations under the License.
 #include "imstkCollidingObject.h"
 #include "imstkLogger.h"
 #include "imstkNew.h"
+#include "imstkPbdConstraintContainer.h"
 #include "imstkPbdModel.h"
 #include "imstkPbdObject.h"
 #include "imstkPbdSolver.h"
@@ -82,7 +83,8 @@ PbdObjectCuttingPair::apply()
 
     // update pbd states, constraints and solver
     pbdModel->initState();
-    pbdModel->removeConstraints(m_removeConstraintVertices);
+    pbdModel->getConstraints()->removeConstraintVertices(m_removeConstraintVertices);
+    // pbdModel->getConstraints()->addConstraintVertices(m_addConstraintVertices);
     pbdModel->addConstraints(m_addConstraintVertices);
     pbdModel->getSolver()->setInvMasses(pbdModel->getInvMasses());
     pbdModel->getSolver()->setPositions(pbdModel->getCurrentState()->getPositions());
diff --git a/Source/Solvers/imstkPbdSolver.cpp b/Source/Solvers/imstkPbdSolver.cpp
index 285184c1fc249e1b6452d34219126c5486eabe27..d10db83aec61c41ffe239e8c706efd619f68e918 100644
--- a/Source/Solvers/imstkPbdSolver.cpp
+++ b/Source/Solvers/imstkPbdSolver.cpp
@@ -23,13 +23,13 @@
 #include "imstkLogger.h"
 #include "imstkParallelUtils.h"
 #include "imstkPbdCollisionConstraint.h"
+#include "imstkPbdConstraintContainer.h"
 
 namespace imstk
 {
 PbdSolver::PbdSolver() :
     m_dt(0.0),
-    m_partitionedConstraints(std::make_shared<std::vector<PBDConstraintVector>>()),
-    m_constraints(std::make_shared<PBDConstraintVector>()),
+    m_constraints(std::make_shared<PbdConstraintContainer>()),
     m_positions(std::make_shared<VecDataArray<double, 3>>()),
     m_invMasses(std::make_shared<DataArray<double>>())
 {
@@ -55,8 +55,8 @@ PbdSolver::solve()
     VecDataArray<double, 3>& currPositions = *m_positions;
     const DataArray<double>& invMasses     = *m_invMasses;
 
-    const PBDConstraintVector&              constraints = *m_constraints;
-    const std::vector<PBDConstraintVector>& partitionedConstraints = *m_partitionedConstraints;
+    const std::vector<std::shared_ptr<PbdConstraint>>&              constraints = m_constraints->getConstraints();
+    const std::vector<std::vector<std::shared_ptr<PbdConstraint>>>& partitionedConstraints = m_constraints->getPartitionedConstraints();
 
     // zero out the Lagrange multiplier
     for (size_t j = 0; j < constraints.size(); ++j)
@@ -66,7 +66,7 @@ PbdSolver::solve()
 
     for (size_t j = 0; j < partitionedConstraints.size(); j++)
     {
-        const PBDConstraintVector& constraintPartition = partitionedConstraints[j];
+        const std::vector<std::shared_ptr<PbdConstraint>>& constraintPartition = partitionedConstraints[j];
         ParallelUtils::parallelFor(constraintPartition.size(),
             [&](const size_t idx) { constraintPartition[idx]->zeroOutLambda(); }
             );
@@ -82,7 +82,7 @@ PbdSolver::solve()
 
         for (size_t j = 0; j < partitionedConstraints.size(); j++)
         {
-            const PBDConstraintVector& constraintPartition = partitionedConstraints[j];
+            const std::vector<std::shared_ptr<PbdConstraint>>& constraintPartition = partitionedConstraints[j];
 
             ParallelUtils::parallelFor(constraintPartition.size(),
                 [&](const size_t idx)
diff --git a/Source/Solvers/imstkPbdSolver.h b/Source/Solvers/imstkPbdSolver.h
index 2e76d48a3931ba5228f840aa63c58059d98dd59a..e50a119626a5351aa73c5ac3ba233c4675c84931 100644
--- a/Source/Solvers/imstkPbdSolver.h
+++ b/Source/Solvers/imstkPbdSolver.h
@@ -27,6 +27,7 @@
 namespace imstk
 {
 class PbdCollisionConstraint;
+class PbdConstraintContainer;
 
 ///
 /// \struct CollisionConstraintData
@@ -70,17 +71,11 @@ public:
     ///
     void setIterations(const size_t iterations) { this->m_iterations = iterations; }
 
-    ///
-    /// \brief Sets the partioned constraints the solver should solve for
-    /// These will be solved in parallel
-    ///
-    void setPartitionedConstraints(std::shared_ptr<std::vector<PBDConstraintVector>> partitionedConstraints) { this->m_partitionedConstraints = partitionedConstraints; }
-
     ///
     /// \brief Sets the constraints the solver should solve for
     /// These wil be solved sequentially
     ///
-    void setConstraints(std::shared_ptr<PBDConstraintVector> constraints) { this->m_constraints = constraints; }
+    void setConstraints(std::shared_ptr<PbdConstraintContainer> constraints) { this->m_constraints = constraints; }
 
     ///
     /// \brief Sets the positions the solver should solve with
@@ -113,11 +108,10 @@ public:
     void solve() override;
 
 private:
-    size_t m_iterations = 20;                                                             ///> Number of NL Gauss-Seidel iterations for regular constraints
-    double m_dt;                                                                          ///> time step
+    size_t m_iterations = 20;                                         ///> Number of NL Gauss-Seidel iterations for regular constraints
+    double m_dt;                                                      ///> time step
 
-    std::shared_ptr<std::vector<PBDConstraintVector>> m_partitionedConstraints = nullptr; ///> Set of vector'd/partitioned pbd constraints
-    std::shared_ptr<PBDConstraintVector> m_constraints = nullptr;                         ///> Vector of constraints
+    std::shared_ptr<PbdConstraintContainer> m_constraints = nullptr;  ///> Vector of constraints
 
     std::shared_ptr<VecDataArray<double, 3>> m_positions = nullptr;
     std::shared_ptr<DataArray<double>>       m_invMasses = nullptr;