diff --git a/CMakeLists.txt b/CMakeLists.txt
index 717eb124c4eb1e08d455bdf73446fabec96a6265..ea981fe4e2f67149ce0f349175cc99d9e8346d17 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,11 +11,11 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pthread -Wall")
 set(CMAKE_EXPORT_COMPILE_COMMANDS "YES")
 
 # generated .a and .so go into /lib
-#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
-#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
 
 include_directories(${CMAKE_SOURCE_DIR}/src)
 include_directories(${CMAKE_SOURCE_DIR})
 
+add_subdirectory(src)
 add_subdirectory(tests)
-
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..90c9d8f8c4446615bfd3e045cd64a082a0d1d016
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(HEADERS
+    swcio.hpp
+    )
+set(BASE_SOURCES
+    swcio.cpp
+)
+
+add_library(cellalgo ${BASE_SOURCES} ${HEADERS})
diff --git a/src/swcio.cpp b/src/swcio.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..129f70afe97cf6a1ca78475205f3d4ac75c54e78
--- /dev/null
+++ b/src/swcio.cpp
@@ -0,0 +1,214 @@
+#include <iomanip>
+#include <map>
+#include <sstream>
+#include <unordered_set>
+#include <swcio.hpp>
+
+namespace nestmc
+{
+namespace io
+{
+
+//
+// cell_record implementation
+// 
+void cell_record::renumber(id_type new_id, std::map<id_type, id_type> &idmap)
+{
+    auto old_id = id_;
+    id_ = new_id;
+
+    // Obtain parent_id from the map
+    auto new_parent_id = idmap.find(parent_id_);
+    if (new_parent_id != idmap.end()) {
+        parent_id_ = new_parent_id->second;
+    }
+
+    check_consistency();
+    idmap.insert(std::make_pair(old_id, new_id));
+}
+
+void cell_record::check_consistency() const
+{
+    // Check cell type as well; enum's do not offer complete type safety,
+    // since you can cast anything that fits to its underlying type
+    if (type_ < 0 || type_ > custom) {
+        throw std::invalid_argument("unknown cell type");
+    }
+
+    if (id_ < 0) {
+        throw std::invalid_argument("negative ids not allowed");
+    }
+        
+    if (parent_id_ < -1) {
+        throw std::invalid_argument("parent_id < -1 not allowed");
+    }
+
+    if (parent_id_ >= id_) {
+        throw std::invalid_argument("parent_id >= id is not allowed");
+    }
+
+    if (r_ < 0) {
+        throw std::invalid_argument("negative radii are not allowed");
+    }
+}
+
+std::istream &operator>>(std::istream &is, cell_record &cell)
+{
+    swc_parser parser;
+    parser.parse_record(is, cell);
+    return is;
+}
+
+
+std::ostream &operator<<(std::ostream &os, const cell_record &cell)
+{
+    // output in one-based indexing
+    os << cell.id_+1 << " "
+       << cell.type_ << " "
+       << std::setprecision(7) << cell.x_ << " "
+       << std::setprecision(7) << cell.y_ << " "
+       << std::setprecision(7) << cell.z_ << " "
+       << std::setprecision(7) << cell.r_ << " "
+       << ((cell.parent_id_ == -1) ? cell.parent_id_ : cell.parent_id_+1);
+
+    return os;
+}
+
+
+//
+// Utility functions
+// 
+
+bool starts_with(const std::string &str, const std::string &prefix)
+{
+    return (str.find(prefix) == 0);
+}
+
+void check_parse_status(const std::istream &is)
+{
+    if (is.fail())
+        // If we try to read past the eof; fail bit will also be set
+        throw swc_parse_error("could not parse value");
+}
+
+template<typename T>
+T parse_value_strict(std::istream &is)
+{
+    T val;
+    check_parse_status(is >> val);
+
+    // everything's fine
+    return val;
+}
+
+// specialize parsing for cell types
+template<>
+cell_record::kind parse_value_strict(std::istream &is)
+{
+    cell_record::id_type val;
+    check_parse_status(is >> val);
+
+    // Let cell_record's constructor check for the type validity
+    return static_cast<cell_record::kind>(val);
+}
+
+//
+// swc_parser implementation
+// 
+
+std::istream &swc_parser::parse_record(std::istream &is, cell_record &cell)
+{
+    while (!is.eof() && !is.bad()) {
+        // consume empty and comment lines first
+        std::getline(is, linebuff_);
+        if (!linebuff_.empty() && !starts_with(linebuff_, comment_prefix_))
+            break;
+    }
+
+    if (is.bad()) {
+        // let the caller check for such events
+        return is;
+    }
+
+    if (is.eof() &&
+        (linebuff_.empty() || starts_with(linebuff_, comment_prefix_))) {
+        // last line is either empty or a comment; don't parse anything
+        return is;
+    }
+
+    if (is.fail()) {
+        throw swc_parse_error("too long line detected");
+    }
+
+    std::istringstream line(linebuff_);
+    cell = parse_record(line);
+    return is;
+}
+
+cell_record swc_parser::parse_record(std::istringstream &is)
+{
+    auto id = parse_value_strict<int>(is);
+    auto type = parse_value_strict<cell_record::kind>(is);
+    auto x = parse_value_strict<float>(is);
+    auto y = parse_value_strict<float>(is);
+    auto z = parse_value_strict<float>(is);
+    auto r = parse_value_strict<float>(is);
+    auto parent_id = parse_value_strict<cell_record::id_type>(is);
+
+    // Convert to zero-based, leaving parent_id as-is if -1
+    if (parent_id != -1) {
+        parent_id--;
+    }
+
+    return cell_record(type, id-1, x, y, z, r, parent_id);
+}
+
+
+std::vector<cell_record> swc_read_cells(std::istream &is)
+{
+    std::vector<cell_record> cells;
+    std::unordered_set<cell_record::id_type> ids;
+
+    std::size_t          num_trees = 0;
+    cell_record::id_type last_id   = -1;
+    bool                 needsort  = false;
+
+    cell_record curr_cell;
+    while ( !(is >> curr_cell).eof()) {
+        if (curr_cell.parent() == -1 && ++num_trees > 1) {
+            // only a single tree is allowed
+            break;
+        }
+
+        auto inserted = ids.insert(curr_cell.id());
+        if (inserted.second) {
+            // not a duplicate; insert cell
+            cells.push_back(curr_cell);
+            if (!needsort && curr_cell.id() < last_id) {
+                needsort = true;
+            }
+
+            last_id = curr_cell.id();
+        }
+    }
+
+    if (needsort) {
+        std::sort(cells.begin(), cells.end());
+    }
+
+    // Renumber cells if necessary
+    std::map<cell_record::id_type, cell_record::id_type> idmap;
+    cell_record::id_type next_id = 0;
+    for (auto &c : cells) {
+        if (c.id() != next_id) {
+            c.renumber(next_id, idmap);
+        }
+
+        ++next_id;
+    }
+
+    return std::move(cells);
+}
+
+}   // end of nestmc::io
+}   // end of nestmc
diff --git a/src/swcio.hpp b/src/swcio.hpp
index d362109dd15b771ac897bb884380682214f113b0..ba52720a7f1852c88cb60ae51b0f1aee9c10f9b9 100644
--- a/src/swcio.hpp
+++ b/src/swcio.hpp
@@ -2,27 +2,23 @@
 
 #include <exception>
 #include <iostream>
-#include <sstream>
-#include <type_traits>
+#include <string>
+#include <vector>
 
-namespace neuron
+namespace nestmc
 {
 
 namespace io
 {
 
 
-static bool starts_with(const std::string &str, const std::string &prefix)
-{
-    return (str.find(prefix) == 0);
-}
-
 class cell_record 
 {
 public:
+    using id_type = int;
 
-    // FIXME: enum's are not completely type-safe, since they can accept anything
-    // that can be casted to their underlying type.
+    // FIXME: enum's are not completely type-safe, since they can accept
+    // anything that can be casted to their underlying type.
     // 
     // More on SWC files: http://research.mssm.edu/cnic/swc.html
     enum kind {
@@ -48,22 +44,7 @@ public:
         , r_(r)
         , parent_id_(parent_id)
     {
-        // Check cell type as well; enum's do not offer complete type safety,
-        // since you can cast anything that fits to its underlying type
-        if (type_ < 0 || type_ > custom)
-            throw std::invalid_argument("unknown cell type");
-
-        if (id_ < 0)
-            throw std::invalid_argument("negative ids not allowed");
-        
-        if (parent_id_ < -1)
-            throw std::invalid_argument("parent_id < -1 not allowed");
-
-        if (parent_id_ >= id_)
-            throw std::invalid_argument("parent_id >= id is not allowed");
-
-        if (r_ < 0)
-            throw std::invalid_argument("negative radii are not allowed");
+        check_consistency();
     }
     
     cell_record()
@@ -79,18 +60,43 @@ public:
     cell_record(const cell_record &other) = default;
     cell_record &operator=(const cell_record &other) = default;
 
+    // Equality and comparison operators
     friend bool operator==(const cell_record &lhs,
                            const cell_record &rhs)
     {
         return lhs.id_ == rhs.id_;
     }
 
+    friend bool operator<(const cell_record &lhs,
+                          const cell_record &rhs)
+    {
+        return lhs.id_ < rhs.id_;
+    }
+
+    friend bool operator<=(const cell_record &lhs,
+                           const cell_record &rhs)
+    {
+        return (lhs < rhs) || (lhs == rhs);
+    }
+
     friend bool operator!=(const cell_record &lhs,
                            const cell_record &rhs)
     {
         return !(lhs == rhs);
     }
 
+    friend bool operator>(const cell_record &lhs,
+                          const cell_record &rhs)
+    {
+        return !(lhs < rhs) && (lhs != rhs);
+    }
+
+    friend bool operator>=(const cell_record &lhs,
+                           const cell_record &rhs)
+    {
+        return !(lhs < rhs);
+    }
+
     friend std::ostream &operator<<(std::ostream &os, const cell_record &cell);
 
     kind type() const
@@ -98,12 +104,12 @@ public:
         return type_;
     }
 
-    int id() const
+    id_type id() const
     {
         return id_;
     }
 
-    int parent() const
+    id_type parent() const
     {
         return parent_id_;
     }
@@ -133,12 +139,28 @@ public:
         return 2*r_;
     }
 
+    void renumber(id_type new_id, std::map<id_type, id_type> &idmap);
+
 private:
+    void check_consistency() const;
+
     kind type_;         // cell type
-    int id_;            // cell id
+    id_type id_;        // cell id
     float x_, y_, z_;   // cell coordinates
     float r_;           // cell radius
-    int parent_id_;     // cell parent's id
+    id_type parent_id_; // cell parent's id
+};
+
+class swc_parse_error : public std::runtime_error
+{
+public:
+    explicit swc_parse_error(const char *msg)
+        : std::runtime_error(msg)
+    { }
+
+    explicit swc_parse_error(const std::string &msg)
+        : std::runtime_error(msg)
+    { }
 };
 
 class swc_parser
@@ -155,51 +177,9 @@ public:
         , comment_prefix_("#")
     { }
 
-    std::istream &parse_record(std::istream &is, cell_record &cell)
-    {
-        while (!is.eof() && !is.bad()) {
-            // consume empty and comment lines first
-            std::getline(is, linebuff_);
-            if (!linebuff_.empty() && !starts_with(linebuff_, comment_prefix_))
-                break;
-        }
-
-        if (is.bad())
-            // let the caller check for such events
-            return is;
-
-        if (is.eof() &&
-            (linebuff_.empty() || starts_with(linebuff_, comment_prefix_)))
-            // last line is either empty or a comment; don't parse anything
-            return is;
-
-        if (is.fail())
-            throw std::runtime_error("too long line detected");
-
-        std::istringstream line(linebuff_);
-        cell = parse_record(line);
-        return is;
-    }
+    std::istream &parse_record(std::istream &is, cell_record &cell);
 
 private:
-    void check_parse_status(const std::istream &is)
-    {
-        if (is.fail())
-            // If we try to read past the eof; fail bit will also be set
-            // FIXME: better throw a custom parse_error exception
-            throw std::logic_error("could not parse value");
-    }
-
-    template<typename T>
-    T parse_value_strict(std::istream &is)
-    {
-        T val;
-        check_parse_status(is >> val);
-
-        // everything's fine
-        return val;
-    }
-
     // Read the record from a string stream; will be treated like a single line
     cell_record parse_record(std::istringstream &is);
 
@@ -209,55 +189,16 @@ private:
 };
 
 
-// specialize parsing for cell types
-template<>
-cell_record::kind swc_parser::parse_value_strict(std::istream &is)
-{
-    int val;
-    check_parse_status(is >> val);
-
-    // Let cell_record's constructor check for the type validity
-    return static_cast<cell_record::kind>(val);
-}
-
-cell_record swc_parser::parse_record(std::istringstream &is)
-{
-    auto id = parse_value_strict<int>(is);
-    auto type = parse_value_strict<cell_record::kind>(is);
-    auto x = parse_value_strict<float>(is);
-    auto y = parse_value_strict<float>(is);
-    auto z = parse_value_strict<float>(is);
-    auto r = parse_value_strict<float>(is);
-    auto parent_id = parse_value_strict<int>(is);
-
-    // Convert to zero-based, leaving parent_id as-is if -1
-    if (parent_id != -1)
-        parent_id--;
-
-    return cell_record(type, id-1, x, y, z, r, parent_id);
-}
+std::istream &operator>>(std::istream &is, cell_record &cell);
 
+//
+// Reads cells from an input stream until an eof is encountered and returns a
+// cleaned sequence of cell records.
+//
+// For more information check here:
+//   https://github.com/eth-cscs/cell_algorithms/wiki/SWC-file-parsing
+//
+std::vector<cell_record> swc_read_cells(std::istream &is);
 
-std::istream &operator>>(std::istream &is, cell_record &cell)
-{
-    swc_parser parser;
-    parser.parse_record(is, cell);
-    return is;
-}
-
-std::ostream &operator<<(std::ostream &os, const cell_record &cell)
-{
-    // output in one-based indexing
-    os << cell.id_+1 << " "
-       << cell.type_ << " "
-       << std::setprecision(7) << cell.x_    << " "
-       << std::setprecision(7) << cell.y_    << " "
-       << std::setprecision(7) << cell.z_    << " "
-       << std::setprecision(7) << cell.r_    << " "
-       << ((cell.parent_id_ == -1) ? cell.parent_id_ : cell.parent_id_+1);
-
-    return os;
-}
-
-}   // end of neuron::io
-}   // end of neuron
+}   // end of nestmc::io
+}   // end of nestmc
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ba8b481ad51109f2a758ebd19c4281d5f3792d2f..7e787ba46b50a48d29ca3d5dd8d70f8116354d5d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -18,10 +18,9 @@ set(TEST_SOURCES
 
 add_executable(test.exe ${TEST_SOURCES} ${HEADERS})
 
-#target_link_libraries(test_compiler LINK_PUBLIC compiler gtest)
-
-#set_target_properties( test.exe
-#   PROPERTIES
-#   RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
-#)
+target_link_libraries(test.exe LINK_PUBLIC cellalgo)
 
+set_target_properties(test.exe
+   PROPERTIES
+   RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
+)
diff --git a/tests/test_swcio.cpp b/tests/test_swcio.cpp
index 5a11a5ed214e884d0e5da248ba2fff2abcaac4b2..a3a7336a8716877cb2a6bc084b4b866a9b58a005 100644
--- a/tests/test_swcio.cpp
+++ b/tests/test_swcio.cpp
@@ -1,3 +1,4 @@
+#include <array>
 #include <iostream>
 #include <fstream>
 #include <numeric>
@@ -8,8 +9,8 @@
 #include <swcio.hpp>
 
 // SWC tests
-void expect_cell_equals(const neuron::io::cell_record &expected,
-                        const neuron::io::cell_record &actual)
+void expect_cell_equals(const nestmc::io::cell_record &expected,
+                        const nestmc::io::cell_record &actual)
 {
     EXPECT_EQ(expected.id(), actual.id());
     EXPECT_EQ(expected.type(), actual.type());
@@ -22,7 +23,7 @@ void expect_cell_equals(const neuron::io::cell_record &expected,
 
 TEST(cell_record, construction)
 {
-    using namespace neuron::io;
+    using namespace nestmc::io;
 
     {
         // force an invalid type
@@ -87,27 +88,45 @@ TEST(cell_record, construction)
     }
 }
 
+TEST(cell_record, comparison)
+{
+    using namespace nestmc::io;
+
+    {
+        // check comparison operators
+        cell_record cell0(cell_record::custom, 0, 1., 1., 1., 1., -1);
+        cell_record cell1(cell_record::custom, 0, 2., 3., 4., 5., -1);
+        cell_record cell2(cell_record::custom, 1, 2., 3., 4., 5., -1);
+        EXPECT_EQ(cell0, cell1);
+        EXPECT_LT(cell0, cell2);
+        EXPECT_GT(cell2, cell1);
+    }
+
+}
+
 TEST(swc_parser, invalid_input)
 {
-    using namespace neuron::io;
+    using namespace nestmc::io;
 
     {
         // check incomplete lines; missing parent
         std::istringstream is("1 1 14.566132 34.873772 7.857000 0.717830\n");
         cell_record cell;
-        EXPECT_THROW(is >> cell, std::logic_error);
+        EXPECT_THROW(is >> cell, swc_parse_error);
     }
 
     {
         // Check non-parsable values
-        std::istringstream is("1a 1 14.566132 34.873772 7.857000 0.717830 -1\n");
+        std::istringstream is(
+            "1a 1 14.566132 34.873772 7.857000 0.717830 -1\n");
         cell_record cell;
-        EXPECT_THROW(is >> cell, std::logic_error);
+        EXPECT_THROW(is >> cell, swc_parse_error);
     }
 
     {
         // Check invalid cell type
-        std::istringstream is("1 10 14.566132 34.873772 7.857000 0.717830 -1\n");
+        std::istringstream is(
+            "1 10 14.566132 34.873772 7.857000 0.717830 -1\n");
         cell_record cell;
         EXPECT_THROW(is >> cell, std::invalid_argument);
     }
@@ -116,7 +135,7 @@ TEST(swc_parser, invalid_input)
 
 TEST(swc_parser, valid_input)
 {
-    using namespace neuron::io;
+    using namespace nestmc::io;
 
     {
         // check empty file; no cell may be parsed
@@ -175,14 +194,14 @@ TEST(swc_parser, valid_input)
                 ++nr_records;
             }
         } catch (std::exception &e) {
-            ADD_FAILURE();
+            ADD_FAILURE() << "unexpected exception thrown\n";
         }
     }
 }
 
 TEST(swc_parser, from_allen_db)
 {
-    using namespace neuron;
+    using namespace nestmc;
 
     auto fname = "../data/example.swc";
     std::ifstream fid(fname);
@@ -197,6 +216,83 @@ TEST(swc_parser, from_allen_db)
     while( !(fid >> node).eof()) {
         nodes.push_back(std::move(node));
     }
+
     // verify that the correct number of nodes was read
     EXPECT_EQ(nodes.size(), 1058u);
 }
+
+TEST(swc_parser, input_cleaning)
+{
+    using namespace nestmc::io;
+    
+    {
+        // Check duplicates
+        std::stringstream is;
+        is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\n";
+        is << "2 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "2 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "2 1 14.566132 34.873772 7.857000 0.717830 1\n";
+
+        auto cells = swc_read_cells(is);
+        EXPECT_EQ(2, cells.size());
+    }
+
+    {
+        // Check multiple trees
+        std::stringstream is;
+        is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\n";
+        is << "2 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "3 1 14.566132 34.873772 7.857000 0.717830 -1\n";
+        is << "4 1 14.566132 34.873772 7.857000 0.717830 1\n";
+
+        auto cells = swc_read_cells(is);
+        EXPECT_EQ(2, cells.size());
+    }
+
+    {
+        // Check unsorted input
+        std::stringstream is;
+        is << "3 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "2 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "4 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\n";
+
+        std::array<cell_record::id_type, 4> expected_id_list = {{ 0, 1, 2, 3 }};
+        auto cells = swc_read_cells(is);
+        ASSERT_EQ(4, cells.size());
+        
+        auto expected_id = expected_id_list.cbegin();
+        for (const auto &c : cells) {
+            EXPECT_EQ(*expected_id, c.id());
+            ++expected_id;
+        }
+    }
+
+    {
+        // Check holes in numbering
+        std::stringstream is;
+        is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\n";
+        is << "21 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "31 1 14.566132 34.873772 7.857000 0.717830 21\n";
+        is << "41 1 14.566132 34.873772 7.857000 0.717830 21\n";
+        is << "51 1 14.566132 34.873772 7.857000 0.717830 1\n";
+        is << "61 1 14.566132 34.873772 7.857000 0.717830 51\n";
+
+        auto cells = swc_read_cells(is);
+        std::array<cell_record::id_type, 6> expected_id_list =
+            {{ 0, 1, 2, 3, 4, 5 }};
+        std::array<cell_record::id_type, 6> expected_parent_list =
+            {{ -1, 0, 1, 1, 0, 4 }};
+        ASSERT_EQ(6, cells.size());
+
+        auto expected_id = expected_id_list.cbegin();
+        auto expected_parent = expected_parent_list.cbegin();
+        for (const auto &c : cells) {
+            EXPECT_EQ(*expected_id, c.id());
+            EXPECT_EQ(*expected_parent, c.parent());
+            ++expected_id;
+            ++expected_parent;
+        }
+        
+    }
+}
diff --git a/vector b/vector
index a8dfadd460262ebbc1bc22b159efe9e33ad1d359..9c86d0a84efed0dd739888503d275378df67fe71 160000
--- a/vector
+++ b/vector
@@ -1 +1 @@
-Subproject commit a8dfadd460262ebbc1bc22b159efe9e33ad1d359
+Subproject commit 9c86d0a84efed0dd739888503d275378df67fe71