Repository: SparseLinearAlgebra/spla (language: python)

Issue:
Vector masked assign
Implement vector masked scalar value assignment (handle coo case only).

## Signature

```c++
RefPtr<ExpressionNode> MakeAssign(const RefPtr<Vector> &w,
                                  const RefPtr<Vector> &mask,
                                  const RefPtr<Scalar> &s,
                                  const RefPtr<Descriptor> &desc = nullptr);
```

`w<mask> = s`
`w<!mask> = s`

- w - vector to store result
- mask - mask to select slots to assign (no mask, whole vector is filled)
- s - scalar value to assign
- mask or !mask is defined by desc (see #85)

## Expected behavior 

`w[i] = s for i in mask or w[i] = s for i in !mask (depends on descriptor)`

## Algo

For each block mask.b[i] of mask dispatch tf::Task to evaluatre w.b[i] = coo_assign(mask.b[i], s)

## Tasks

- [x] Setup signature
- [x] Setup tests
- [x] Setup coo algo `ParamsVectorAssign`
- [x] Implement coo algo
- [x] Implement expr node
- [x] No values case


Discussion / hints:

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fca3e4c0d..cf987b9fa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -214,7 +214,8 @@ add_library(spla SHARED
         ${SPLA_COMPUTE_SOURCES}
         ${SPLA_CORE_SOURCES}
         ${SPLA_EXPRESSION_SOURCES}
-        ${SPLA_STORAGE_SOURCES})
+        ${SPLA_STORAGE_SOURCES}
+        ${SPLA_UTILS_SOURCES})
 
 target_include_directories(spla PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
 target_include_directories(spla PRIVATE ${CMAKE_CURRENT_LIST_DIR}/sources)
diff --git a/include/spla-cpp/SplaDescriptor.hpp b/include/spla-cpp/SplaDescriptor.hpp
index 8ac4a5866..2dda1441c 100644
--- a/include/spla-cpp/SplaDescriptor.hpp
+++ b/include/spla-cpp/SplaDescriptor.hpp
@@ -59,6 +59,8 @@ namespace spla {
             NoDuplicates,
             /** Apply !mask (complementary mask) to result or input arguments */
             MaskComplement,
+            /** Apply binary op accum to output and temporary operation result */
+            ApplyAccum,
             /** Profiles time of the operation and outputs result to the log */
             ProfileTime,
             /** Transpose operation arg 1 matrix before operation */
diff --git a/include/spla-cpp/SplaExpression.hpp b/include/spla-cpp/SplaExpression.hpp
index 43af7170e..b03deac69 100644
--- a/include/spla-cpp/SplaExpression.hpp
+++ b/include/spla-cpp/SplaExpression.hpp
@@ -211,6 +211,24 @@ namespace spla {
                                             const RefPtr<DataScalar> &data,
                                             const RefPtr<Descriptor> &desc = nullptr);
 
+        /**
+         * Make vector masked scalar value assignment node.
+         * Operation is evaluated as `w<mask> = accum(w, s)`
+         *
+         * @param w Vector to store result
+         * @param mask Mask to select indices to assign; may be null
+         * @param accum Binary function used to accum old and new values; may be null
+         * @param s Input scalar; null for void values
+         * @param desc Operation descriptor
+         *
+         * @return Created expression node
+         */
+        RefPtr<ExpressionNode> MakeAssign(const RefPtr<Vector> &w,
+                                          const RefPtr<Vector> &mask,
+                                          const RefPtr<FunctionBinary> &accum,
+                                          const RefPtr<Scalar> &s,
+                                          const RefPtr<Descriptor> &desc = nullptr);
+
         /**
          * Make matrix masked element-wise add expression node.
          * Operation is evaluated as `w<mask> = a +(op) b`.
diff --git a/include/spla-cpp/SplaExpressionNode.hpp b/include/spla-cpp/SplaExpressionNode.hpp
index 464d33307..7b170daf8 100644
--- a/include/spla-cpp/SplaExpressionNode.hpp
+++ b/include/spla-cpp/SplaExpressionNode.hpp
@@ -65,6 +65,8 @@ namespace spla {
             ScalarDataRead,
             /** Write scalar data from the host buffers */
             ScalarDataWrite,
+            /** Vector scalar assignment */
+            VectorAssign,
             /** Matrix element-wise addition */
             MatrixEWiseAdd,
             /** Vector element-wise addition */
@@ -136,6 +138,8 @@ namespace spla {
                     return "ScalarDataRead";
                 case ExpressionNode::Operation::ScalarDataWrite:
                     return "ScalarDataWrite";
+                case ExpressionNode::Operation::VectorAssign:
+                    return "VectorAssign";
                 case ExpressionNode::Operation::MatrixEWiseAdd:
                     return "MatrixEWiseAdd";
                 case ExpressionNode::Operation::VectorEWiseAdd:
diff --git a/sources/CMakeLists.txt b/sources/CMakeLists.txt
index 4832ed8a7..ad4d75630 100644
--- a/sources/CMakeLists.txt
+++ b/sources/CMakeLists.txt
@@ -20,6 +20,8 @@ set(SPLA_ALGORITHM_SOURCES
         sources/algo/matrix/SplaMatrixEWiseAddCOO.hpp
         sources/algo/mxm/SplaMxMCOO.cpp
         sources/algo/mxm/SplaMxMCOO.hpp
+        sources/algo/vector/SplaVectorAssignCOO.cpp
+        sources/algo/vector/SplaVectorAssignCOO.hpp
         sources/algo/vector/SplaVectorEWiseAddCOO.cpp
         sources/algo/vector/SplaVectorEWiseAddCOO.hpp
         sources/algo/vxm/SplaVxMCOO.cpp
@@ -69,6 +71,8 @@ set(SPLA_EXPRESSION_SOURCES
         sources/expression/scalar/SplaScalarDataWrite.hpp
         sources/expression/scalar/SplaScalarDataRead.cpp
         sources/expression/scalar/SplaScalarDataRead.hpp
+        sources/expression/vector/SplaVectorAssign.cpp
+        sources/expression/vector/SplaVectorAssign.hpp
         sources/expression/vector/SplaVectorDataWrite.cpp
         sources/expression/vector/SplaVectorDataWrite.hpp
         sources/expression/vector/SplaVectorDataRead.cpp
@@ -81,6 +85,9 @@ set(SPLA_EXPRESSION_SOURCES
         sources/expression/SplaExpressionTasks.hpp
         sources/expression/SplaNodeProcessor.hpp)
 
+set(SPLA_UTILS_SOURCES
+        sources/utils/SplaAlgo.hpp)
+
 set(SPLA_STORAGE_SOURCES
         sources/storage/block/SplaMatrixCOO.cpp
         sources/storage/block/SplaMatrixCOO.hpp
diff --git a/sources/SplaExpression.cpp b/sources/SplaExpression.cpp
index 5ca21375a..15c68331b 100644
--- a/sources/SplaExpression.cpp
+++ b/sources/SplaExpression.cpp
@@ -201,6 +201,29 @@ spla::Expression::MakeDataRead(const RefPtr<Scalar> &scalar,
                     desc);
 }
 
+spla::RefPtr<spla::ExpressionNode>
+spla::Expression::MakeAssign(const spla::RefPtr<spla::Vector> &w,
+                             const spla::RefPtr<spla::Vector> &mask,
+                             const spla::RefPtr<FunctionBinary> &accum,
+                             const spla::RefPtr<spla::Scalar> &s,
+                             const spla::RefPtr<spla::Descriptor> &desc) {
+    CHECK_RAISE_ERROR(w.IsNotNull(), NullPointer, "w can't be null");
+    CHECK_RAISE_ERROR(s.IsNull() || w->IsCompatible(*s), InvalidType, "s and w must have the same type");
+    CHECK_RAISE_ERROR(s.IsNotNull() || !w->GetType()->HasValues(), InvalidState, "s can be null only if w has no values");
+    CHECK_RAISE_ERROR(mask.IsNull() || w->GetNrows() == mask->GetNrows(), DimensionMismatch, "Incompatible size");
+    CHECK_RAISE_ERROR(accum.IsNull() || (s.IsNotNull() && accum->CanApply(*w, *s, *w)), InvalidType, "cannot apply provided accum function");
+
+    std::vector<RefPtr<Object>> args = {
+            w.As<Object>(),
+            mask.As<Object>(),
+            accum.As<Object>(),
+            s.As<Object>()};
+
+    return MakeNode(ExpressionNode::Operation::VectorAssign,
+                    std::move(args),
+                    desc);
+}
+
 spla::RefPtr<spla::ExpressionNode>
 spla::Expression::MakeEWiseAdd(const spla::RefPtr<spla::Matrix> &w,
                                const spla::RefPtr<spla::Matrix> &mask,
diff --git a/sources/algo/SplaAlgorithm.hpp b/sources/algo/SplaAlgorithm.hpp
index f6750838e..468656dd1 100644
--- a/sources/algo/SplaAlgorithm.hpp
+++ b/sources/algo/SplaAlgorithm.hpp
@@ -62,6 +62,8 @@ namespace spla {
             MatrixEWiseAdd,
             /** Matrix-matrix element wise multiplication */
             MatrixEWiseMult,
+            /** Vector-scalar assignment */
+            VectorAssign,
             /** Vector-vector element wise addition */
             VectorEWiseAdd,
             /** Vector-vector element wise multiplication */
@@ -109,6 +111,8 @@ namespace spla {
                     return "MatrixEWiseAdd";
                 case Algorithm::Type::MatrixEWiseMult:
                     return "MatrixEWiseMult";
+                case Algorithm::Type::VectorAssign:
+                    return "VectorAssign";
                 case Algorithm::Type::VectorEWiseAdd:
                     return "VectorEWiseAdd";
                 case Algorithm::Type::VectorEWiseMult:
diff --git a/sources/algo/SplaAlgorithmManager.cpp b/sources/algo/SplaAlgorithmManager.cpp
index eecb3568d..96f6f4493 100644
--- a/sources/algo/SplaAlgorithmManager.cpp
+++ b/sources/algo/SplaAlgorithmManager.cpp
@@ -28,6 +28,7 @@
 #include <algo/SplaAlgorithmManager.hpp>
 #include <algo/matrix/SplaMatrixEWiseAddCOO.hpp>
 #include <algo/mxm/SplaMxMCOO.hpp>
+#include <algo/vector/SplaVectorAssignCOO.hpp>
 #include <algo/vector/SplaVectorEWiseAddCOO.hpp>
 #include <algo/vxm/SplaVxMCOO.hpp>
 #include <cassert>
@@ -35,6 +36,7 @@
 
 spla::AlgorithmManager::AlgorithmManager(Library &library) : mLibrary(library) {
     Register(new MatrixEWiseAddCOO());
+    Register(new VectorAssignCOO());
     Register(new VectorEWiseAddCOO());
     Register(new MxMCOO());
     Register(new VxMCOO());
diff --git a/sources/algo/SplaAlgorithmParams.hpp b/sources/algo/SplaAlgorithmParams.hpp
index 507429032..a53dd8123 100644
--- a/sources/algo/SplaAlgorithmParams.hpp
+++ b/sources/algo/SplaAlgorithmParams.hpp
@@ -33,6 +33,7 @@
 #include <spla-cpp/SplaFunctionBinary.hpp>
 #include <spla-cpp/SplaRefCnt.hpp>
 #include <storage/SplaMatrixBlock.hpp>
+#include <storage/SplaScalarValue.hpp>
 #include <storage/SplaVectorBlock.hpp>
 
 namespace spla {
@@ -122,6 +123,19 @@ namespace spla {
         RefPtr<Type> tw;
     };
 
+    /** Blocked vector-scalar assignment params */
+    class ParamsVectorAssign final : public AlgorithmParams {
+    public:
+        ~ParamsVectorAssign() override = default;
+
+        std::size_t size;        // size of block
+        bool hasMask = false;    // true if must apply mask
+        RefPtr<VectorBlock> w;   // type
+        RefPtr<VectorBlock> mask;// if has mask, must apply this
+        RefPtr<ScalarValue> s;   // type (or null)
+        RefPtr<Type> type;
+    };
+
     /**
      * @}
      */
diff --git a/sources/algo/vector/SplaVectorAssignCOO.cpp b/sources/algo/vector/SplaVectorAssignCOO.cpp
new file mode 100644
index 000000000..dc6631e24
--- /dev/null
+++ b/sources/algo/vector/SplaVectorAssignCOO.cpp
@@ -0,0 +1,114 @@
+/**********************************************************************************/
+/* This file is part of spla project                                              */
+/* https://github.com/JetBrains-Research/spla                                     */
+/**********************************************************************************/
+/* MIT License                                                                    */
+/*                                                                                */
+/* Copyright (c) 2021 JetBrains-Research                                          */
+/*                                                                                */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
+/* of this software and associated documentation files (the "Software"), to deal  */
+/* in the Software without restriction, including without limitation the rights   */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
+/* copies of the Software, and to permit persons to whom the Software is          */
+/* furnished to do so, subject to the following conditions:                       */
+/*                                                                                */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software.                                */
+/*                                                                                */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
+/* SOFTWARE.                                                                      */
+/**********************************************************************************/
+
+#include <algo/vector/SplaVectorAssignCOO.hpp>
+#include <compute/SplaGather.hpp>
+#include <compute/SplaMaskByKey.hpp>
+#include <core/SplaLibraryPrivate.hpp>
+#include <core/SplaQueueFinisher.hpp>
+#include <storage/block/SplaMatrixCOO.hpp>
+#include <storage/block/SplaVectorCOO.hpp>
+
+bool spla::VectorAssignCOO::Select(const spla::AlgorithmParams &params) const {
+    auto p = dynamic_cast<const ParamsVectorAssign *>(&params);
+
+    return p &&
+           p->mask.Is<VectorCOO>();
+}
+
+void spla::VectorAssignCOO::Process(spla::AlgorithmParams &params) {
+    using namespace boost;
+
+    auto p = dynamic_cast<ParamsVectorAssign *>(&params);
+    auto library = p->desc->GetLibrary().GetPrivatePtr();
+    auto &desc = p->desc;
+
+    auto device = library->GetDeviceManager().GetDevice(p->deviceId);
+    compute::context ctx = library->GetContext();
+    compute::command_queue queue(ctx, device);
+    QueueFinisher finisher(queue);
+
+    auto s = p->s;
+    auto size = p->size;
+    auto type = p->type;
+    auto mask = p->mask.Cast<VectorCOO>();
+    auto complementMask = desc->IsParamSet(Descriptor::Param::MaskComplement);
+
+    // Indices and values of the result
+    compute::vector<unsigned int> rows(ctx);
+    compute::vector<unsigned char> vals(ctx);
+
+    // Assign full result
+    if (!p->hasMask || (mask.IsNull() && complementMask)) {
+        rows.resize(size, queue);
+        compute::copy_n(compute::counting_iterator<unsigned int>(0), size, rows.begin(), queue);
+    }
+    // Has mask, must filter
+    else {
+        // Nothing to do
+        if (mask.IsNull())
+            return;
+
+        // Apply direct mask
+        if (!complementMask) {
+            auto maskSize = mask->GetNvals();
+            rows.resize(maskSize, queue);
+            compute::copy_n(mask->GetRows().begin(), maskSize, rows.begin(), queue);
+        }
+        // Apply complement mask
+        else {
+            // Tmp indices to filter
+            compute::vector<unsigned int> indices(size, ctx);
+            compute::copy_n(compute::counting_iterator<unsigned int>(0), size, indices.begin(), queue);
+
+            // Filter indices and save in rows result
+            MaskKeys(mask->GetRows(), indices, rows, complementMask, queue);
+        }
+    }
+
+    auto nvals = rows.size();
+
+    // If type has values, gather it (for each nnz value assign scalar value)
+    if (nvals > 0 && type->HasValues()) {
+        vals.resize(nvals * type->GetByteSize(), queue);
+        auto mapIdBegin = compute::constant_iterator<unsigned int>(0, 0);
+        auto mapIdEnd = compute::constant_iterator<unsigned int>(0, nvals);
+        Gather(mapIdBegin, mapIdEnd, s->GetVal().begin(), vals.begin(), type->GetByteSize(), queue);
+    }
+
+    // If after masking has values, store new block
+    if (nvals)
+        p->w = VectorCOO::Make(size, nvals, std::move(rows), std::move(vals)).As<VectorBlock>();
+}
+
+spla::Algorithm::Type spla::VectorAssignCOO::GetType() const {
+    return spla::Algorithm::Type::VectorAssign;
+}
+
+std::string spla::VectorAssignCOO::GetName() const {
+    return "VectorAssignCOO";
+}
\ No newline at end of file
diff --git a/sources/algo/vector/SplaVectorAssignCOO.hpp b/sources/algo/vector/SplaVectorAssignCOO.hpp
new file mode 100644
index 000000000..ee4f90126
--- /dev/null
+++ b/sources/algo/vector/SplaVectorAssignCOO.hpp
@@ -0,0 +1,44 @@
+/**********************************************************************************/
+/* This file is part of spla project                                              */
+/* https://github.com/JetBrains-Research/spla                                     */
+/**********************************************************************************/
+/* MIT License                                                                    */
+/*                                                                                */
+/* Copyright (c) 2021 JetBrains-Research                                          */
+/*                                                                                */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
+/* of this software and associated documentation files (the "Software"), to deal  */
+/* in the Software without restriction, including without limitation the rights   */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
+/* copies of the Software, and to permit persons to whom the Software is          */
+/* furnished to do so, subject to the following conditions:                       */
+/*                                                                                */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software.                                */
+/*                                                                                */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
+/* SOFTWARE.                                                                      */
+/**********************************************************************************/
+
+#ifndef SPLA_SPLAVECTORASSIGNCOO_HPP
+#define SPLA_SPLAVECTORASSIGNCOO_HPP
+
+#include <algo/SplaAlgorithm.hpp>
+
+namespace spla {
+    class VectorAssignCOO final : public Algorithm {
+    public:
+        ~VectorAssignCOO() override = default;
+        bool Select(const AlgorithmParams &params) const override;
+        void Process(AlgorithmParams &params) override;
+        Type GetType() const override;
+        std::string GetName() const override;
+    };
+}// namespace spla
+
+#endif//SPLA_SPLAVECTORASSIGNCOO_HPP
diff --git a/sources/algo/vxm/SplaVxMCOO.cpp b/sources/algo/vxm/SplaVxMCOO.cpp
index 59e93b56c..f6f5e923d 100644
--- a/sources/algo/vxm/SplaVxMCOO.cpp
+++ b/sources/algo/vxm/SplaVxMCOO.cpp
@@ -53,7 +53,6 @@ void spla::VxMCOO::Process(spla::AlgorithmParams &params) {
     using namespace boost;
 
     auto p = dynamic_cast<ParamsVxM *>(&params);
-    auto w = p->w;
     auto library = p->desc->GetLibrary().GetPrivatePtr();
     auto &desc = p->desc;
 
diff --git a/sources/expression/SplaExpressionManager.cpp b/sources/expression/SplaExpressionManager.cpp
index 655fb86a6..66e86eccb 100644
--- a/sources/expression/SplaExpressionManager.cpp
+++ b/sources/expression/SplaExpressionManager.cpp
@@ -41,6 +41,7 @@
 #include <expression/prod/SplaVxM.hpp>
 #include <expression/scalar/SplaScalarDataRead.hpp>
 #include <expression/scalar/SplaScalarDataWrite.hpp>
+#include <expression/vector/SplaVectorAssign.hpp>
 #include <expression/vector/SplaVectorDataRead.hpp>
 #include <expression/vector/SplaVectorDataWrite.hpp>
 #include <expression/vector/SplaVectorEWiseAdd.hpp>
@@ -53,6 +54,7 @@ spla::ExpressionManager::ExpressionManager(spla::Library &library) : mLibrary(li
     Register(new MatrixEWiseAdd());
     Register(new ScalarDataRead());
     Register(new ScalarDataWrite());
+    Register(new VectorAssign());
     Register(new VectorDataWrite());
     Register(new VectorDataRead());
     Register(new VectorEWiseAdd());
@@ -88,18 +90,15 @@ void spla::ExpressionManager::Submit(const spla::RefPtr<spla::Expression> &expre
                                                     << "; Possibly have some dependency cycle?");
 
     CheckCycles(context);
-    DefineTraversalPath(context);
 
-    auto &traversal = context.traversal;
     auto &nodes = expression->GetNodes();
-
     auto expressionTasks = std::make_unique<ExpressionTasks>();
     auto &taskflow = expressionTasks->taskflow;
 
     std::vector<tf::Task> modules;
     modules.reserve(nodes.size());
 
-    for (auto idx : traversal) {
+    for (std::size_t idx = 0; idx < nodes.size(); idx++) {
         // Select processor for node
         auto processor = SelectProcessor(idx, *expression);
         // Wrap processor into task to handle dynamic changes of expression nodes params
@@ -123,7 +122,7 @@ void spla::ExpressionManager::Submit(const spla::RefPtr<spla::Expression> &expre
         modules.push_back(taskflow.emplace(std::move(task)));
     }
 
-    for (std::size_t idx : traversal) {
+    for (std::size_t idx = 0; idx < nodes.size(); idx++) {
         // Compose final taskflow graph
         auto &node = nodes[idx];
         auto &next = node->GetNext();
@@ -219,53 +218,6 @@ bool spla::ExpressionManager::CheckCyclesImpl(std::size_t idx, std::vector<int>
     return false;
 }
 
-void spla::ExpressionManager::DefineTraversalPath(spla::ExpressionManager::TraversalInfo &context) {
-    auto &expression = context.expression;
-    auto &nodes = expression->GetNodes();
-    auto &start = context.startNodes;
-    auto nodesCount = nodes.size();
-
-    std::size_t t = 0;
-    std::vector<std::size_t> out(nodesCount, t);
-
-    for (auto s : start)
-        DefineTraversalPathImpl(s, t, out, nodes);
-
-    using Pair = std::pair<std::size_t, std::size_t>;
-    std::vector<Pair> traversal;
-
-    traversal.reserve(nodesCount);
-    for (std::size_t i = 0; i < nodesCount; i++)
-        traversal.emplace_back(out[i], i);
-
-    std::sort(traversal.begin(), traversal.end(), [](const Pair &a, const Pair &b) {
-        return a.first > b.first;
-    });
-
-    context.traversal.resize(nodesCount);
-
-    std::transform(traversal.begin(), traversal.end(), context.traversal.begin(), [](const Pair &a) -> std::size_t {
-        return a.second;
-    });
-}
-
-void spla::ExpressionManager::DefineTraversalPathImpl(std::size_t idx, std::size_t &t, std::vector<std::size_t> &out,
-                                                      const std::vector<RefPtr<ExpressionNode>> &nodes) {
-    if (!out[idx]) {
-        t += 1;
-
-        auto &node = nodes[idx];
-        auto &next = node->GetNext();
-
-        for (const auto &n : next)
-            DefineTraversalPathImpl(n->GetIdx(), t, out, nodes);
-
-        t += 1;
-
-        out[idx] = t;
-    }
-}
-
 spla::RefPtr<spla::NodeProcessor>
 spla::ExpressionManager::SelectProcessor(std::size_t nodeIdx, const spla::Expression &expression) {
     auto &nodes = expression.GetNodes();
diff --git a/sources/expression/SplaExpressionManager.hpp b/sources/expression/SplaExpressionManager.hpp
index ad35f8844..7097f26ae 100644
--- a/sources/expression/SplaExpressionManager.hpp
+++ b/sources/expression/SplaExpressionManager.hpp
@@ -65,8 +65,6 @@ namespace spla {
             std::vector<std::size_t> startNodes;
             /** Final nodes */
             std::vector<std::size_t> endNodes;
-            /** Order of the expression traversal to submit computation */
-            std::vector<std::size_t> traversal;
         };
 
         explicit ExpressionManager(Library &library);
@@ -80,8 +78,6 @@ namespace spla {
         void FindEndNodes(TraversalInfo &context);
         void CheckCycles(TraversalInfo &context);
         bool CheckCyclesImpl(std::size_t idx, std::vector<int> &visited, const std::vector<RefPtr<ExpressionNode>> &nodes);
-        void DefineTraversalPath(TraversalInfo &context);
-        void DefineTraversalPathImpl(std::size_t idx, std::size_t &t, std::vector<std::size_t> &out, const std::vector<RefPtr<ExpressionNode>> &nodes);
 
         RefPtr<NodeProcessor> SelectProcessor(std::size_t nodeIdx, const Expression &expression);
 
diff --git a/sources/expression/scalar/SplaScalarDataWrite.cpp b/sources/expression/scalar/SplaScalarDataWrite.cpp
index 23436972d..dab268a81 100644
--- a/sources/expression/scalar/SplaScalarDataWrite.cpp
+++ b/sources/expression/scalar/SplaScalarDataWrite.cpp
@@ -40,6 +40,7 @@ void spla::ScalarDataWrite::Process(std::size_t nodeIdx, const spla::Expression
     auto &nodes = expression.GetNodes();
     auto &node = nodes[nodeIdx];
     auto &library = node->GetLibrary().GetPrivate();
+    auto &logger = library.GetLogger();
 
     auto scalar = node->GetArg(0).Cast<Scalar>();
     auto data = node->GetArg(1).Cast<DataScalar>();
@@ -69,6 +70,8 @@ void spla::ScalarDataWrite::Process(std::size_t nodeIdx, const spla::Expression
 
     auto scalarValue = ScalarValue::Make(std::move(deviceValue));
     scalar->GetStorage()->SetValue(scalarValue);
+
+    SPDLOG_LOGGER_TRACE(logger, "Write value byteSize={}", byteSize);
 }
 
 spla::ExpressionNode::Operation spla::ScalarDataWrite::GetOperationType() const {
diff --git a/sources/expression/vector/SplaVectorAssign.cpp b/sources/expression/vector/SplaVectorAssign.cpp
new file mode 100644
index 000000000..41b4f56e9
--- /dev/null
+++ b/sources/expression/vector/SplaVectorAssign.cpp
@@ -0,0 +1,124 @@
+/**********************************************************************************/
+/* This file is part of spla project                                              */
+/* https://github.com/JetBrains-Research/spla                                     */
+/**********************************************************************************/
+/* MIT License                                                                    */
+/*                                                                                */
+/* Copyright (c) 2021 JetBrains-Research                                          */
+/*                                                                                */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
+/* of this software and associated documentation files (the "Software"), to deal  */
+/* in the Software without restriction, including without limitation the rights   */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
+/* copies of the Software, and to permit persons to whom the Software is          */
+/* furnished to do so, subject to the following conditions:                       */
+/*                                                                                */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software.                                */
+/*                                                                                */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
+/* SOFTWARE.                                                                      */
+/**********************************************************************************/
+
+#include <core/SplaLibraryPrivate.hpp>
+#include <core/SplaMath.hpp>
+#include <expression/vector/SplaVectorAssign.hpp>
+#include <storage/SplaScalarStorage.hpp>
+#include <storage/SplaVectorStorage.hpp>
+#include <utils/SplaAlgo.hpp>
+
+bool spla::VectorAssign::Select(std::size_t nodeIdx, const spla::Expression &expression) {
+    return true;
+}
+
+void spla::VectorAssign::Process(std::size_t nodeIdx, const spla::Expression &expression, spla::TaskBuilder &builder) {
+    auto &nodes = expression.GetNodes();
+    auto &node = nodes[nodeIdx];
+    auto library = node->GetLibrary().GetPrivatePtr();
+    auto logger = library->GetLogger();
+    auto &deviceMan = library->GetDeviceManager();
+
+    auto w = node->GetArg(0).Cast<Vector>();
+    auto mask = node->GetArg(1).Cast<Vector>();
+    auto accum = node->GetArg(2).Cast<FunctionBinary>();
+    auto s = node->GetArg(3).Cast<Scalar>();
+    auto desc = node->GetDescriptor();
+
+    assert(w.IsNotNull());
+    assert(desc.IsNotNull());
+
+    /** Handle case if new to accum(w, s) */
+    auto tmp = w;
+    auto applyAccum = desc->IsParamSet(Descriptor::Param::ApplyAccum);
+
+    // Create temporary vector for assignment result
+    if (applyAccum) tmp = Vector::Make(w->GetNrows(), w->GetType(), w->GetLibrary());
+
+    // If no accum, clear result first
+    if (!applyAccum) w->GetStorage()->Clear();
+
+    // If assign is null, make default to keep new entries
+    if (applyAccum && accum.IsNull()) accum = utils::MakeFunctionChooseSecond(w->GetType());
+
+    std::size_t requiredDeviceCount = w->GetStorage()->GetNblockRows();
+    auto deviceIds = library->GetDeviceManager().FetchDevices(requiredDeviceCount, node);
+
+    for (std::size_t i = 0; i < w->GetStorage()->GetNblockRows(); i++) {
+        auto deviceId = deviceIds[i];
+
+        auto assignmentTask = builder.Emplace([=]() {
+            auto blockIdx = i;
+            auto blockSize = w->GetStorage()->GetBlockSize();
+            auto dim = w->GetStorage()->GetNrows();
+
+            ParamsVectorAssign params;
+            params.desc = desc;
+            params.deviceId = deviceId;
+            params.size = math::GetBlockActualSize(blockIdx, dim, blockSize);
+            params.hasMask = mask.IsNotNull();
+            params.mask = mask.IsNotNull() ? mask->GetStorage()->GetBlock(i) : RefPtr<VectorBlock>{};
+            params.s = s.IsNotNull() ? s->GetStorage()->GetValue() : RefPtr<ScalarValue>{};
+            params.type = w->GetType();
+            library->GetAlgoManager()->Dispatch(Algorithm::Type::VectorAssign, params);
+
+            if (params.w.IsNotNull()) {
+                tmp->GetStorage()->SetBlock(i, params.w);
+                SPDLOG_LOGGER_TRACE(logger, "Assign block i={} nnz={}", i, params.w->GetNvals());
+            }
+        });
+
+        if (applyAccum) {
+            auto accumTask = builder.Emplace([=]() {
+                auto tmpBlock = w->GetStorage()->GetBlock(i);
+
+                if (tmpBlock.IsNotNull()) {
+                    ParamsVectorEWiseAdd params;
+                    params.desc = desc;
+                    params.deviceId = deviceId;
+                    params.a = w->GetStorage()->GetBlock(i);
+                    params.b = tmp->GetStorage()->GetBlock(i);
+                    params.op = accum;
+                    params.type = w->GetType();
+                    library->GetAlgoManager()->Dispatch(Algorithm::Type::VectorEWiseAdd, params);
+
+                    if (params.w.IsNotNull()) {
+                        w->GetStorage()->SetBlock(i, params.w);
+                        SPDLOG_LOGGER_TRACE(logger, "Accum block i={} nnz={}", i, params.w->GetNvals());
+                    }
+                }
+            });
+
+            // Assign and then accum result
+            assignmentTask.precede(accumTask);
+        }
+    }
+}
+
+spla::ExpressionNode::Operation spla::VectorAssign::GetOperationType() const {
+    return spla::ExpressionNode::Operation::VectorAssign;
+}
diff --git a/sources/expression/vector/SplaVectorAssign.hpp b/sources/expression/vector/SplaVectorAssign.hpp
new file mode 100644
index 000000000..73093f96e
--- /dev/null
+++ b/sources/expression/vector/SplaVectorAssign.hpp
@@ -0,0 +1,45 @@
+/**********************************************************************************/
+/* This file is part of spla project                                              */
+/* https://github.com/JetBrains-Research/spla                                     */
+/**********************************************************************************/
+/* MIT License                                                                    */
+/*                                                                                */
+/* Copyright (c) 2021 JetBrains-Research                                          */
+/*                                                                                */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
+/* of this software and associated documentation files (the "Software"), to deal  */
+/* in the Software without restriction, including without limitation the rights   */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
+/* copies of the Software, and to permit persons to whom the Software is          */
+/* furnished to do so, subject to the following conditions:                       */
+/*                                                                                */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software.                                */
+/*                                                                                */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
+/* SOFTWARE.                                                                      */
+/**********************************************************************************/
+
+#ifndef SPLA_SPLAVECTORASSIGN_HPP
+#define SPLA_SPLAVECTORASSIGN_HPP
+
+#include <expression/SplaNodeProcessor.hpp>
+
+namespace spla {
+
+    class VectorAssign final : public NodeProcessor {
+    public:
+        ~VectorAssign() override = default;
+        bool Select(std::size_t nodeIdx, const Expression &expression) override;
+        void Process(std::size_t nodeIdx, const Expression &expression, TaskBuilder &builder) override;
+        ExpressionNode::Operation GetOperationType() const override;
+    };
+
+}// namespace spla
+
+#endif//SPLA_SPLAVECTORASSIGN_HPP
diff --git a/sources/expression/vector/SplaVectorDataWrite.cpp b/sources/expression/vector/SplaVectorDataWrite.cpp
index 33d2acaaf..f03d6058c 100644
--- a/sources/expression/vector/SplaVectorDataWrite.cpp
+++ b/sources/expression/vector/SplaVectorDataWrite.cpp
@@ -26,9 +26,9 @@
 /**********************************************************************************/
 
 #include <boost/compute/algorithm.hpp>
-#include <boost/compute/container/vector.hpp>
 #include <boost/compute/iterator.hpp>
 #include <compute/SplaGather.hpp>
+#include <compute/SplaSortByRow.hpp>
 #include <core/SplaLibraryPrivate.hpp>
 #include <core/SplaMath.hpp>
 #include <core/SplaQueueFinisher.hpp>
@@ -158,21 +158,7 @@ void spla::VectorDataWrite::Process(std::size_t nodeIdx, const spla::Expression
             // If entries are not sorted, we must sort it here in row-cols order
             if (!desc->IsParamSet(Descriptor::Param::ValuesSorted) && blockNvals > 1) {
                 SPDLOG_LOGGER_TRACE(logger, "Sort vector block {} entries", i);
-
-                compute::vector<unsigned int> permutation(blockNvals, ctx);
-                compute::copy(compute::counting_iterator<cl_uint>(0),
-                              compute::counting_iterator<cl_uint>(blockNvals),
-                              permutation.begin(),
-                              queue);
-
-                compute::sort_by_key(blockRows.begin(), blockRows.end(), permutation.begin(), queue);
-
-                // Copy values, using permutation
-                if (typeHasValues) {
-                    compute::vector<unsigned char> valsTmp(blockNvals * byteSize, ctx);
-                    Gather(permutation.begin(), permutation.end(), blockVals.begin(), valsTmp.begin(), byteSize, queue);
-                    std::swap(blockVals, valsTmp);
-                }
+                SortByRow(blockRows, blockVals, byteSize, queue);
             }
 
             if (!desc->IsParamSet(Descriptor::Param::NoDuplicates) && blockNvals > 1) {
diff --git a/sources/storage/SplaVectorStorage.cpp b/sources/storage/SplaVectorStorage.cpp
index b42cad8d5..c2433c8d8 100644
--- a/sources/storage/SplaVectorStorage.cpp
+++ b/sources/storage/SplaVectorStorage.cpp
@@ -101,6 +101,10 @@ std::size_t spla::VectorStorage::GetNblockRows() const noexcept {
     return mNblockRows;
 }
 
+std::size_t spla::VectorStorage::GetBlockSize() const noexcept {
+    return mBlockSize;
+}
+
 void spla::VectorStorage::Dump(std::ostream &stream) const {
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -118,4 +122,4 @@ void spla::VectorStorage::Dump(std::ostream &stream) const {
         stream << "Block (" << index << ") ";
         block->Dump(stream, index * bsize);
     }
-}
+}
\ No newline at end of file
diff --git a/sources/storage/SplaVectorStorage.hpp b/sources/storage/SplaVectorStorage.hpp
index ebc79a801..a82e8d52e 100644
--- a/sources/storage/SplaVectorStorage.hpp
+++ b/sources/storage/SplaVectorStorage.hpp
@@ -84,6 +84,9 @@ namespace spla {
         /** @return Number of values in storage */
         [[nodiscard]] std::size_t GetNvals() const noexcept;
 
+        /** @return Block size param */
+        [[nodiscard]] std::size_t GetBlockSize() const noexcept;
+
         /** Dump vector content to provided stream */
         void Dump(std::ostream &stream) const;
 
diff --git a/sources/utils/SplaAlgo.hpp b/sources/utils/SplaAlgo.hpp
new file mode 100644
index 000000000..973aa7a77
--- /dev/null
+++ b/sources/utils/SplaAlgo.hpp
@@ -0,0 +1,64 @@
+/**********************************************************************************/
+/* This file is part of spla project                                              */
+/* https://github.com/JetBrains-Research/spla                                     */
+/**********************************************************************************/
+/* MIT License                                                                    */
+/*                                                                                */
+/* Copyright (c) 2021 JetBrains-Research                                          */
+/*                                                                                */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
+/* of this software and associated documentation files (the "Software"), to deal  */
+/* in the Software without restriction, including without limitation the rights   */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
+/* copies of the Software, and to permit persons to whom the Software is          */
+/* furnished to do so, subject to the following conditions:                       */
+/*                                                                                */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software.                                */
+/*                                                                                */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
+/* SOFTWARE.                                                                      */
+/**********************************************************************************/
+
+#ifndef SPLA_SPLAALGO_HPP
+#define SPLA_SPLAALGO_HPP
+
+#include <spla-cpp/SplaFunctionBinary.hpp>
+#include <spla-cpp/SplaType.hpp>
+#include <sstream>
+
+namespace spla {
+
+    /**
+     * @addtogroup Internal
+     * @{
+     */
+
+    namespace utils {
+
+        /** @return Generic function `f x y = y` */
+        inline RefPtr<FunctionBinary> MakeFunctionChooseSecond(const RefPtr<Type> &t) {
+            std::stringstream body;
+
+            body << "_ACCESS_B const uchar * b = (_ACCESS_B const uchar *)vp_b;\n"
+                 << "_ACCESS_C uchar * c = (_ACCESS_C uchar *)vp_c;\n"
+                 << "for (uint i = 0; i < " << t->GetByteSize() << "; i++)\n"
+                 << "   c[i] = b[i];\n";
+
+            return FunctionBinary::Make(t, t, t, body.str(), t->GetLibrary());
+        }
+
+    }// namespace utils
+
+    /**
+     * @}
+     */
+
+}// namespace spla
+
+#endif//SPLA_SPLAALGO_HPP


Test patch:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 502a7f18d..bc5736620 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -17,5 +17,6 @@ spla_test_target(TestMergeByKey)
 spla_test_target(TestMxM)
 spla_test_target(TestReduceByKey)
 spla_test_target(TestReduceDuplicates)
+spla_test_target(TestVectorAssign)
 spla_test_target(TestVectorEWiseAdd)
 spla_test_target(TestVxM)
diff --git a/tests/TestVectorAssign.cpp b/tests/TestVectorAssign.cpp
new file mode 100644
index 000000000..aff988d38
--- /dev/null
+++ b/tests/TestVectorAssign.cpp
@@ -0,0 +1,232 @@
+/**********************************************************************************/
+/* This file is part of spla project                                              */
+/* https://github.com/JetBrains-Research/spla                                     */
+/**********************************************************************************/
+/* MIT License                                                                    */
+/*                                                                                */
+/* Copyright (c) 2021 JetBrains-Research                                          */
+/*                                                                                */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy   */
+/* of this software and associated documentation files (the "Software"), to deal  */
+/* in the Software without restriction, including without limitation the rights   */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      */
+/* copies of the Software, and to permit persons to whom the Software is          */
+/* furnished to do so, subject to the following conditions:                       */
+/*                                                                                */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software.                                */
+/*                                                                                */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  */
+/* SOFTWARE.                                                                      */
+/**********************************************************************************/
+
+#include <Testing.hpp>
+
+template<typename Type, typename BinaryOp>
+void testMasked(spla::Library &library, std::size_t M, std::size_t nvals,
+                const spla::RefPtr<spla::Type> &spT,
+                const spla::RefPtr<spla::FunctionBinary> &spAccum,
+                BinaryOp accum, std::size_t seed = 0) {
+    auto applyAccum = spAccum.IsNotNull();
+    Type s = utils::UniformGenerator<Type>()();
+    utils::Vector w = applyAccum ? utils::Vector<Type>::Generate(M, nvals, seed).SortReduceDuplicates() : utils::Vector<Type>::Empty(M);
+    utils::Vector mask = utils::Vector<unsigned char>::Generate(M, nvals, seed + 1).SortReduceDuplicates();
+
+    w.Fill(utils::UniformGenerator<Type>());
+
+    auto spW = spla::Vector::Make(M, spT, library);
+    auto spS = spla::Scalar::Make(spT, library);
+    auto spMask = spla::Vector::Make(M, spla::Types::Void(library), library);
+
+    // Specify, that values already in row order + no duplicates
+    auto spDesc = spla::Descriptor::Make(library);
+    spDesc->SetParam(spla::Descriptor::Param::ValuesSorted);
+    spDesc->SetParam(spla::Descriptor::Param::NoDuplicates);
+
+    auto spOpDesc = spla::Descriptor::Make(library);
+    if (applyAccum) spOpDesc->SetParam(spla::Descriptor::Param::ApplyAccum);
+
+    auto spExpr = spla::Expression::Make(library);
+    auto spWriteS = spExpr->MakeDataWrite(spS, utils::GetData(s, library));
+    auto spWriteMask = spExpr->MakeDataWrite(spMask, mask.GetDataIndices(library), spDesc);
+    auto spAssign = spExpr->MakeAssign(spW, spMask, spAccum, spS, spOpDesc);
+    spExpr->Dependency(spWriteS, spAssign);
+    spExpr->Dependency(spWriteMask, spAssign);
+
+    if (applyAccum) {
+        auto spWriteW = spExpr->MakeDataWrite(spW, w.GetData(library), spDesc);
+        spExpr->Dependency(spWriteW, spAssign);
+    }
+
+    spExpr->Submit();
+    spExpr->Wait();
+    ASSERT_EQ(spExpr->GetState(), spla::Expression::State::Evaluated);
+
+    utils::Vector<Type> c = w.Assign(mask, false, s, accum);
+    ASSERT_TRUE(c.Equals(spW));
+}
+
+template<typename Type, typename BinaryOp>
+void testMaskedComplement(spla::Library &library, std::size_t M, std::size_t nvals,
+                          const spla::RefPtr<spla::Type> &spT,
+                          const spla::RefPtr<spla::FunctionBinary> &spAccum,
+                          BinaryOp accum, std::size_t seed = 0) {
+    auto applyAccum = spAccum.IsNotNull();
+    Type s = utils::UniformGenerator<Type>()();
+    utils::Vector w = applyAccum ? utils::Vector<Type>::Generate(M, nvals, seed).SortReduceDuplicates() : utils::Vector<Type>::Empty(M);
+    utils::Vector mask = utils::Vector<unsigned char>::Generate(M, nvals, seed + 1).SortReduceDuplicates();
+
+    w.Fill(utils::UniformGenerator<Type>());
+
+    auto spW = spla::Vector::Make(M, spT, library);
+    auto spS = spla::Scalar::Make(spT, library);
+    auto spMask = spla::Vector::Make(M, spla::Types::Void(library), library);
+
+    // Specify, that values already in row order + no duplicates
+    auto spDesc = spla::Descriptor::Make(library);
+    spDesc->SetParam(spla::Descriptor::Param::ValuesSorted);
+    spDesc->SetParam(spla::Descriptor::Param::NoDuplicates);
+
+    auto spOpDesc = spla::Descriptor::Make(library);
+    spOpDesc->SetParam(spla::Descriptor::Param::MaskComplement);
+    if (applyAccum) spOpDesc->SetParam(spla::Descriptor::Param::ApplyAccum);
+
+    auto spExpr = spla::Expression::Make(library);
+    auto spWriteS = spExpr->MakeDataWrite(spS, utils::GetData(s, library));
+    auto spWriteMask = spExpr->MakeDataWrite(spMask, mask.GetDataIndices(library), spDesc);
+    auto spAssign = spExpr->MakeAssign(spW, spMask, spAccum, spS, spOpDesc);
+    spExpr->Dependency(spWriteS, spAssign);
+    spExpr->Dependency(spWriteMask, spAssign);
+
+    if (applyAccum) {
+        auto spWriteW = spExpr->MakeDataWrite(spW, w.GetData(library), spDesc);
+        spExpr->Dependency(spWriteW, spAssign);
+    }
+
+    spExpr->Submit();
+    spExpr->Wait();
+    ASSERT_EQ(spExpr->GetState(), spla::Expression::State::Evaluated);
+
+    utils::Vector<Type> c = w.Assign(mask, true, s, accum);
+    ASSERT_TRUE(c.Equals(spW));
+}
+
+void testNoValues(spla::Library &library, std::size_t M, std::size_t nvals, std::size_t seed = 0) {
+    utils::Vector w = utils::Vector<unsigned char>::Generate(M, nvals, seed).SortReduceDuplicates();
+    utils::Vector mask = utils::Vector<unsigned char>::Generate(M, nvals, seed + 1).SortReduceDuplicates();
+
+    auto spT = spla::Types::Void(library);
+    auto spW = spla::Vector::Make(M, spT, library);
+    auto spMask = spla::Vector::Make(M, spT, library);
+
+    // Specify, that values already in row order + no duplicates
+    auto spDesc = spla::Descriptor::Make(library);
+    spDesc->SetParam(spla::Descriptor::Param::ValuesSorted);
+    spDesc->SetParam(spla::Descriptor::Param::NoDuplicates);
+
+    auto spOpDesc = spla::Descriptor::Make(library);
+    spOpDesc->SetParam(spla::Descriptor::Param::ApplyAccum);
+
+    auto spExpr = spla::Expression::Make(library);
+    auto spWriteW = spExpr->MakeDataWrite(spW, w.GetDataIndices(library), spDesc);
+    auto spWriteMask = spExpr->MakeDataWrite(spMask, mask.GetDataIndices(library), spDesc);
+    auto spAssign = spExpr->MakeAssign(spW, spMask, nullptr, nullptr, spOpDesc);
+    spExpr->Dependency(spWriteW, spAssign);
+    spExpr->Dependency(spWriteMask, spAssign);
+    spExpr->Submit();
+    spExpr->Wait();
+    ASSERT_EQ(spExpr->GetState(), spla::Expression::State::Evaluated);
+
+    utils::Vector<unsigned char> c = w.Assign(mask, false, 0, [](unsigned char, unsigned char) { return 0; });
+    ASSERT_TRUE(c.EqualsStructure(spW));
+}
+
+void test(std::size_t M, std::size_t base, std::size_t step, std::size_t iter, const std::vector<std::size_t> &blocksSizes) {
+    utils::testBlocks(blocksSizes, [=](spla::Library &library) {
+        using Type = float;
+        auto spT = spla::Types::Float32(library);
+        auto spAccum = spla::Functions::PlusFloat32(library);
+        auto accum = [](Type x, Type y) { return x + y; };
+        auto accumNull = [](Type, Type y) { return y; };
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMasked<Type>(library, M, nvals, spT, spAccum, accum, i);
+        }
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMasked<Type>(library, M, nvals, spT, nullptr, accumNull, i);
+        }
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMaskedComplement<Type>(library, M, nvals, spT, spAccum, accum, i);
+        }
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMaskedComplement<Type>(library, M, nvals, spT, nullptr, accumNull, i);
+        }
+    });
+
+    utils::testBlocks(blocksSizes, [=](spla::Library &library) {
+        using Type = std::int32_t;
+        auto spT = spla::Types::Int32(library);
+        auto spAccum = spla::Functions::PlusInt32(library);
+        auto accum = [](Type x, Type y) { return x + y; };
+        auto accumNull = [](Type, Type y) { return y; };
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMasked<Type>(library, M, nvals, spT, spAccum, accum, i);
+        }
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMasked<Type>(library, M, nvals, spT, nullptr, accumNull, i);
+        }
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMaskedComplement<Type>(library, M, nvals, spT, spAccum, accum, i);
+        }
+
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testMaskedComplement<Type>(library, M, nvals, spT, nullptr, accumNull, i);
+        }
+    });
+
+    utils::testBlocks(blocksSizes, [=](spla::Library &library) {
+        for (std::size_t i = 0; i < iter; i++) {
+            std::size_t nvals = base + i * step;
+            testNoValues(library, M, nvals, i);
+        }
+    });
+}
+
+TEST(VectorAssign, Small) {
+    std::vector<std::size_t> blocksSizes{100, 1000};
+    std::size_t M = 100;
+    test(M, M / 2, M / 10, 10, blocksSizes);
+}
+
+TEST(VectorAssign, Medium) {
+    std::vector<std::size_t> blocksSizes{100, 1000, 10000};
+    std::size_t M = 1100;
+    test(M, M / 2, M / 10, 10, blocksSizes);
+}
+
+TEST(VectorAssign, Large) {
+    std::vector<std::size_t> blocksSizes{1000, 10000, 100000};
+    std::size_t M = 10300;
+    test(M, M / 2, M / 10, 5, blocksSizes);
+}
+
+SPLA_GTEST_MAIN
\ No newline at end of file
diff --git a/tests/utils/Setup.hpp b/tests/utils/Setup.hpp
index 5a86fbfb6..538535b07 100644
--- a/tests/utils/Setup.hpp
+++ b/tests/utils/Setup.hpp
@@ -29,18 +29,28 @@
 #define SPLA_SETUP_HPP
 
 #include <cstddef>
+#include <spla-cpp/Spla.hpp>
 #include <vector>
 
 namespace utils {
 
     template<typename Callable>
-    void testBlocks(const std::vector<std::size_t> &blocksSizes, Callable callable) {
+    inline void testBlocks(const std::vector<std::size_t> &blocksSizes, Callable callable) {
         for (std::size_t blockSize : blocksSizes) {
             spla::Library library(spla::Library::Config().SetBlockSize(blockSize));
             callable(library);
         }
     }
 
+    template<typename T>
+    inline spla::RefPtr<spla::DataScalar> GetData(T s, spla::Library &library) {
+        T *scalar = new T(s);
+        auto data = spla::DataScalar::Make(library);
+        data->SetValue(scalar);
+        data->SetReleaseProc([=](void *scalar) { delete ((T *) scalar); });
+        return data;
+    }
+
 }// namespace utils
 
 #endif//SPLA_SETUP_HPP
diff --git a/tests/utils/Vector.hpp b/tests/utils/Vector.hpp
index 992c10965..6b5e30eca 100644
--- a/tests/utils/Vector.hpp
+++ b/tests/utils/Vector.hpp
@@ -342,6 +342,18 @@ namespace utils {
             return Mask(mask, complement).EWiseAdd(other.Mask(mask, complement), op);
         }
 
+        template<typename M, typename BinaryOp>
+        [[nodiscard]] Vector Assign(const Vector<M> &mask, bool complement, T scalar, BinaryOp accum) {
+            std::vector<Index> rows(mNrows);
+            std::vector<T> vals(mNrows, scalar);
+
+            for (Index i = 0; i < mNrows; i++)
+                rows[i] = i;
+
+            auto tmp = Vector<T>(GetNrows(), std::move(rows), std::move(vals)).Mask(mask, complement);
+            return EWiseAdd(tmp, accum);
+        }
+
         static Vector Generate(std::size_t nrows, std::size_t nvals, std::size_t seed = 0, const T &value = T()) {
             std::vector<Index> rows;
             std::vector<T> vals(nvals, value);