diff --git a/src/util/counter.hpp b/src/util/counter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d071752807f53fc72324367d4e4ea87e0dc6c7be
--- /dev/null
+++ b/src/util/counter.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+/* Present an integral value as an iterator, for integral-range 'containers' */
+
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+template <typename V, typename = typename std::enable_if<std::is_integral<V>::value>::type>
+struct counter {
+    using difference_type = V;
+    using value_type = V;
+    using pointer = const V*;
+    using reference = const V&;
+    using iterator_category = std::random_access_iterator_tag;
+
+    counter(): v_{} {}
+    counter(V v): v_{v} {}
+
+    counter(const counter&) = default;
+    counter(counter&&) = default;
+
+    counter& operator++() {
+        ++v_;
+        return *this;
+    }
+
+    counter operator++(int) {
+        counter c(*this);
+        ++v_;
+        return c;
+    }
+
+    counter& operator--() {
+        --v_;
+        return *this;
+    }
+
+    counter operator--(int) {
+        counter c(*this);
+        --v_;
+        return c;
+    }
+
+    counter& operator+=(difference_type n) {
+        v_ += n;
+        return *this;
+    }
+
+    counter& operator-=(difference_type n) {
+        v_ -= n;
+        return *this;
+    }
+
+    counter operator+(difference_type n) {
+        return counter(v_+n);
+    }
+
+    friend counter operator+(difference_type n, counter x) {
+        return counter(n+x.v_);
+    }
+
+    counter operator-(difference_type n) {
+        return counter(v_-n);
+    }
+
+    difference_type operator-(counter x) const {
+        return v_-x.v_;
+    }
+
+    value_type operator*() const { return v_; }
+
+    value_type operator[](difference_type n) const { return v_+n; }
+
+    bool operator==(counter x) const { return v_==x.v_; }
+    bool operator!=(counter x) const { return v_!=x.v_; }
+    bool operator<=(counter x) const { return v_<=x.v_; }
+    bool operator>=(counter x) const { return v_>=x.v_; }
+    bool operator<(counter x) const { return v_<x.v_; }
+    bool operator>(counter x) const { return v_>x.v_; }
+
+    counter& operator=(const counter&) = default;
+    counter& operator=(counter&&) = default;
+
+private:
+    V v_;
+};
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/debug.cpp b/src/util/debug.cpp
index 51c646bbcb38b1722e40766f4886a957094bd1bb..f2f039708a860c7fcb9ec65db7345b608a019dd9 100644
--- a/src/util/debug.cpp
+++ b/src/util/debug.cpp
@@ -14,8 +14,11 @@ namespace util {
 
 std::mutex global_debug_cerr_mutex;
 
-bool failed_assertion(const char* assertion, const char* file,
-                      int line, const char* func)
+bool abort_on_failed_assertion(
+    const char* assertion,
+    const char* file,
+    int line,
+    const char* func)
 {
     // Explicit flush, as we can't assume default buffering semantics on stderr/cerr,
     // and abort() might not flush streams.
@@ -26,8 +29,13 @@ bool failed_assertion(const char* assertion, const char* file,
     return false;
 }
 
-std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file,
-                                      int line, const char* varlist)
+failed_assertion_handler_t global_failed_assertion_handler = abort_on_failed_assertion;
+
+std::ostream& debug_emit_trace_leader(
+    std::ostream& out,
+    const char* file,
+    int line,
+    const char* varlist)
 {
     iosfmt_guard guard(out);
 
diff --git a/src/util/debug.hpp b/src/util/debug.hpp
index f258b37123755c7ec7a7324641249ed35290e8bb..be2b3d51e4d7bf9b5d4c5fb9b7c78855c5e4b4c5 100644
--- a/src/util/debug.hpp
+++ b/src/util/debug.hpp
@@ -10,7 +10,17 @@ namespace nest {
 namespace mc {
 namespace util {
 
-bool failed_assertion(const char* assertion, const char* file, int line, const char* func);
+using failed_assertion_handler_t =
+    bool (*)(const char* assertion, const char* file, int line, const char* func);
+
+bool abort_on_failed_assertion(const char* assertion, const char* file, int line, const char* func);
+inline bool ignore_failed_assertion(const char*, const char*, int, const char*) {
+    return false;
+}
+
+// defaults to abort_on_failed_assertion;
+extern failed_assertion_handler_t global_failed_assertion_handler;
+
 std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* varlist);
 
 inline void debug_emit(std::ostream& out) {
@@ -66,7 +76,7 @@ void debug_emit_trace(const char* file, int line, const char* varlist, const Arg
 
     #define EXPECTS(condition) \
        (void)((condition) || \
-       nest::mc::util::failed_assertion(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME))
+       nest::mc::util::global_failed_assertion_handler(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME))
 #else
     #define EXPECTS(condition)
 #endif // def WITH_ASSERTIONS
diff --git a/src/util/either.hpp b/src/util/either.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..36932c96e8be340638c98eb582d06ffa93450828
--- /dev/null
+++ b/src/util/either.hpp
@@ -0,0 +1,387 @@
+#pragma once
+
+/*
+ * A type-safe discriminated union of two members.
+ *
+ * Returns true in a bool context if the first of the two types holds a value.
+ */
+
+#include <cstdlib>
+#include <type_traits>
+#include <stdexcept>
+#include <utility>
+
+#include "util/meta.hpp"
+#include "util/uninitialized.hpp"
+
+namespace nest {
+namespace mc {
+namespace util {
+
+struct either_invalid_access: std::runtime_error {
+    explicit either_invalid_access(const std::string& what_str)
+        : std::runtime_error(what_str)
+    {}
+
+    either_invalid_access()
+        : std::runtime_error("access of unconstructed value in either")
+    {}
+};
+
+namespace detail {
+    template <typename A, typename B>
+    struct either_data {
+        union {
+            uninitialized<A> ua;
+            uninitialized<B> ub;
+        };
+
+        either_data() = default;
+
+        either_data(const either_data&) = delete;
+        either_data(either_data&&) = delete;
+        either_data& operator=(const either_data&) = delete;
+        either_data& operator=(either_data&&) = delete;
+    };
+
+    template <std::size_t, typename A, typename B> struct either_select;
+
+    template <typename A, typename B>
+    struct either_select<0, A, B> {
+        using type = uninitialized<A>;
+        static type& field(either_data<A, B>& data) { return data.ua; }
+        static const type& field(const either_data<A, B>& data) { return data.ua; }
+    };
+
+    template <typename A, typename B>
+    struct either_select<1, A, B> {
+        using type = uninitialized<B>;
+        static type& field(either_data<A, B>& data) { return data.ub; }
+        static const type& field(const either_data<A, B>& data) { return data.ub; }
+    };
+
+    template <std::size_t I, typename A, typename B>
+    struct either_get: either_select<I, A, B> {
+        using typename either_select<I, A, B>::type;
+        using either_select<I, A, B>::field;
+
+        static typename type::reference unsafe_get(either_data<A, B>& data) {
+            return field(data).ref();
+        }
+
+        static typename type::const_reference unsafe_get(const either_data<A, B>& data) {
+            return field(data).cref();
+        }
+
+        static typename type::reference unsafe_get(char which, either_data<A, B>& data) {
+            if (I!=which) {
+                throw either_invalid_access();
+            }
+            return field(data).ref();
+        }
+
+        static typename type::const_reference unsafe_get(char which, const either_data<A, B>& data) {
+            if (I!=which) {
+                throw either_invalid_access();
+            }
+            return field(data).cref();
+        }
+
+        static typename type::pointer ptr(char which, either_data<A, B>& data) {
+            return I==which? field(data).ptr(): nullptr;
+        }
+
+        static typename type::const_pointer ptr(char which, const either_data<A, B>& data) {
+            return I==which? field(data).cptr(): nullptr;
+        }
+    };
+} // namespace detail
+
+constexpr std::size_t variant_npos = -1; // emulating C++17 variant type 
+
+template <typename A, typename B>
+class either: public detail::either_data<A, B> {
+    using base = detail::either_data<A, B>;
+    using base::ua;
+    using base::ub;
+
+    template <std::size_t I>
+    using getter = detail::either_get<I, A, B>;
+
+    unsigned char which;
+
+public:
+    // default ctor if A is default-constructible or A is not and B is.
+    template <
+        typename A_ = A,
+        bool a_ = std::is_default_constructible<A_>::value,
+        bool b_ = std::is_default_constructible<B>::value,
+        typename = enable_if_t<a_ || (!a_ && b_)>,
+        std::size_t w_ = a_? 0: 1
+    >
+    either() noexcept(std::is_nothrow_default_constructible<typename getter<w_>::type>::value):
+        which(w_)
+    {
+        getter<w_>::field(*this).construct();
+    }
+
+    // implicit constructors from A and B values by copy or move
+    either(const A& a) noexcept(std::is_nothrow_copy_constructible<A>::value): which(0) {
+        getter<0>::field(*this).construct(a);
+    }
+
+    template <
+        typename B_ = B,
+        typename = enable_if_t<!std::is_same<A, B_>::value>
+    >
+    either(const B& b) noexcept(std::is_nothrow_copy_constructible<B>::value): which(1) {
+        getter<1>::field(*this).construct(b);
+    }
+
+    either(A&& a) noexcept(std::is_nothrow_move_constructible<A>::value): which(0) {
+        getter<0>::field(*this).construct(std::move(a));
+    }
+
+    template <
+        typename B_ = B,
+        typename = enable_if_t<!std::is_same<A, B_>::value>
+    >
+    either(B&& b) noexcept(std::is_nothrow_move_constructible<B>::value): which(1) {
+        getter<1>::field(*this).construct(std::move(b));
+    }
+
+    // copy constructor
+    either(const either& x)
+        noexcept(std::is_nothrow_copy_constructible<A>::value &&
+            std::is_nothrow_copy_constructible<B>::value):
+        which(x.which)
+    {
+        if (which==0) {
+            getter<0>::field(*this).construct(x.unsafe_get<0>());
+        }
+        else if (which==1) {
+            getter<1>::field(*this).construct(x.unsafe_get<1>());
+        }
+    }
+
+    // move constructor
+    either(either&& x)
+        noexcept(std::is_nothrow_move_constructible<A>::value &&
+            std::is_nothrow_move_constructible<B>::value):
+        which(x.which)
+    {
+        if (which==0) {
+            getter<0>::field(*this).construct(std::move(x.unsafe_get<0>()));
+        }
+        else if (which==1) {
+            getter<1>::field(*this).construct(std::move(x.unsafe_get<1>()));
+        }
+    }
+
+    // copy assignment
+    either& operator=(const either& x) {
+        if (this==&x) {
+            return *this;
+        }
+
+        switch (which) {
+        case 0:
+            if (x.which==0) {
+                unsafe_get<0>() = x.unsafe_get<0>();
+            }
+            else {
+                if (x.which==1) {
+                    B b_tmp(x.unsafe_get<1>());
+                    getter<0>::field(*this).destruct();
+                    which = (unsigned char)variant_npos;
+                    getter<1>::field(*this).construct(std::move(b_tmp));
+                    which = 1;
+                }
+                else {
+                    getter<0>::field(*this).destruct();
+                    which = (unsigned char)variant_npos;
+                }
+            }
+            break;
+        case 1:
+            if (x.which==1) {
+                unsafe_get<1>() = x.unsafe_get<1>();
+            }
+            else {
+                if (x.which==0) {
+                    A a_tmp(x.unsafe_get<0>());
+                    getter<1>::field(*this).destruct();
+                    which = (unsigned char)variant_npos;
+                    getter<0>::field(*this).construct(std::move(a_tmp));
+                    which = 0;
+                }
+                else {
+                    getter<1>::field(*this).destruct();
+                    which = (unsigned char)variant_npos;
+                }
+            }
+            break;
+        default: // variant_npos
+            if (x.which==0) {
+                getter<0>::field(*this).construct(x.unsafe_get<0>());
+            }
+            else if (x.which==1) {
+                getter<1>::field(*this).construct(x.unsafe_get<1>());
+            }
+            break;
+        }
+        return *this;
+    }
+
+    // move assignment
+    either& operator=(either&& x) {
+        if (this==&x) {
+            return *this;
+        }
+
+        switch (which) {
+        case 0:
+            if (x.which==0) {
+                unsafe_get<0>() = std::move(x.unsafe_get<0>());
+            }
+            else {
+                which = (unsigned char)variant_npos;
+                getter<0>::field(*this).destruct();
+                if (x.which==1) {
+                    getter<1>::field(*this).construct(std::move(x.unsafe_get<1>()));
+                    which = 1;
+                }
+            }
+            break;
+        case 1:
+            if (x.which==1) {
+                unsafe_get<1>() = std::move(x.unsafe_get<1>());
+            }
+            else {
+                which = (unsigned char)variant_npos;
+                getter<1>::field(*this).destruct();
+                if (x.which==0) {
+                    getter<0>::field(*this).construct(std::move(x.unsafe_get<0>()));
+                    which = 0;
+                }
+            }
+            break;
+        default: // variant_npos
+            if (x.which==0) {
+                getter<0>::field(*this).construct(std::move(x.unsafe_get<0>()));
+            }
+            else if (x.which==1) {
+                getter<1>::field(*this).construct(std::move(x.unsafe_get<1>()));
+            }
+            break;
+        }
+        return *this;
+    }
+
+    // unchecked element access
+    template <std::size_t I>
+    typename getter<I>::type::reference unsafe_get() {
+        return getter<I>::unsafe_get(*this);
+    }
+
+    template <std::size_t I>
+    typename getter<I>::type::const_reference unsafe_get() const {
+        return getter<I>::unsafe_get(*this);
+    }
+
+    // checked element access
+    template <std::size_t I>
+    typename getter<I>::type::reference get() {
+        return getter<I>::unsafe_get(which, *this);
+    }
+
+    template <std::size_t I>
+    typename getter<I>::type::const_reference get() const {
+        return getter<I>::unsafe_get(which, *this);
+    }
+
+    // convenience getter aliases
+    typename getter<0>::type::reference first() { return get<0>(); }
+    typename getter<0>::type::const_reference first() const { return get<0>(); }
+
+    typename getter<1>::type::reference second() { return get<1>(); }
+    typename getter<1>::type::const_reference second() const  { return get<1>(); }
+
+    // pointer to element access: return nullptr if it does not hold this item
+    template <std::size_t I>
+    auto ptr() -> decltype(getter<I>::ptr(which, *this)) {
+        return getter<I>::ptr(which, *this);
+    }
+
+    template <std::size_t I>
+    auto ptr() const -> decltype(getter<I>::ptr(which, *this)) {
+        return getter<I>::ptr(which, *this);
+    }
+
+    // true in bool context if holds first alternative
+    constexpr operator bool() const { return which==0; }
+
+    constexpr bool valueless_by_exception() const noexcept {
+        return which==(unsigned char)variant_npos;
+    }
+
+    constexpr std::size_t index() const noexcept {
+        return which;
+    }
+
+    ~either() {
+        if (which==0) {
+            getter<0>::field(*this).destruct();
+        }
+        else if (which==1) {
+            getter<1>::field(*this).destruct();
+        }
+    }
+
+    // comparison operators follow C++17 variant semantics
+    bool operator==(const either& x) const {
+        return index()==x.index() &&
+           index()==0? unsafe_get<0>()==x.unsafe_get<0>():
+           index()==1? unsafe_get<1>()==x.unsafe_get<1>():
+           true;
+    }
+
+    bool operator!=(const either& x) const {
+        return index()!=x.index() ||
+           index()==0? unsafe_get<0>()!=x.unsafe_get<0>():
+           index()==1? unsafe_get<1>()!=x.unsafe_get<1>():
+           false;
+    }
+
+    bool operator<(const either& x) const {
+        return !x.valueless_by_exception() &&
+           index()==0? (x.index()==1 || unsafe_get<0>()<x.unsafe_get<0>()):
+           index()==1? (x.index()!=0 && unsafe_get<1>()<x.unsafe_get<1>()):
+           true;
+    }
+
+    bool operator>=(const either& x) const {
+        return x.valueless_by_exception() ||
+           index()==0? (x.index()!=1 && unsafe_get<0>()>=x.unsafe_get<0>()):
+           index()==1? (x.index()==0 || unsafe_get<1>()>=x.unsafe_get<1>()):
+           false;
+    }
+
+    bool operator<=(const either& x) const {
+        return valueless_by_exception() ||
+           x.index()==0? (index()!=1 && unsafe_get<0>()<=x.unsafe_get<0>()):
+           x.index()==1? (index()==0 || unsafe_get<1>()<=x.unsafe_get<1>()):
+           false;
+    }
+
+    bool operator>(const either& x) const {
+        return !valueless_by_exception() &&
+           x.index()==0? (index()==1 || unsafe_get<0>()>x.unsafe_get<0>()):
+           x.index()==1? (index()!=0 && unsafe_get<1>()>x.unsafe_get<1>()):
+           true;
+    }
+};
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/iterutil.hpp b/src/util/iterutil.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a9fd159ffe9f75eddf57f2adb5c4d42cb9e7861
--- /dev/null
+++ b/src/util/iterutil.hpp
@@ -0,0 +1,178 @@
+#pragma once
+
+/*
+ * Utilities and base classes to help with
+ * implementing iterators and iterator adaptors.
+ */
+
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <util/meta.hpp>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+/*
+ * Return the iterator reachable from iter such that
+ * std::next(iter)==end
+ *
+ * Two implementations: the first applies generally, while the
+ * second is used when we can just return std::prev(end).
+ */
+template <typename I, typename E>
+enable_if_t<
+    is_forward_iterator<I>::value &&
+        (!is_bidirectional_iterator<E>::value || !std::is_constructible<I, E>::value),
+    I>
+upto(I iter, E end) {
+    I j = iter;
+    while (j!=end) {
+        iter = j;
+        ++j;
+    }
+    return iter;
+}
+
+template <typename I, typename E>
+enable_if_t<is_bidirectional_iterator<E>::value && std::is_constructible<I, E>::value, I>
+upto(I iter, E end) {
+    return iter==I{end}? iter: I{std::prev(end)};
+}
+
+/*
+ * Provide a proxy object for operator->() for iterator adaptors that
+ * present rvalues on dereference.
+ */
+template <typename V>
+struct pointer_proxy: public V {
+    pointer_proxy(const V& v): V{v} {}
+    pointer_proxy(V&& v): V{std::move(v)} {}
+    const V* operator->() const { return this; }
+};
+
+/*
+ * Base class (using CRTP) for iterator adaptors that
+ * perform a transformation or present a proxy for
+ * an underlying iterator.
+ *
+ * Supplies default implementations for iterator concepts
+ * in terms of the derived class' methods and the
+ * inner iterator.
+ *
+ * Derived class must provide implementations for:
+ *   operator*()
+ *   operator[](difference_type)
+ *   inner()   // provides access to wrapped iterator
+ */
+
+template <typename Derived, typename I>
+class iterator_adaptor {
+protected:
+    Derived& derived() { return static_cast<Derived&>(*this); }
+    const Derived& derived() const { return static_cast<const Derived&>(*this); }
+
+private:
+    // Access to inner iterator provided by derived class.
+    I& inner() { return derived().inner(); }
+    const I& inner() const { return derived().inner(); }
+
+public:
+    using value_type = typename std::iterator_traits<I>::value_type;
+    using difference_type = typename std::iterator_traits<I>::difference_type;
+    using iterator_category = typename std::iterator_traits<I>::iterator_category;
+    using pointer = typename std::iterator_traits<I>::pointer;
+    using reference = typename std::iterator_traits<I>::reference;
+
+    iterator_adaptor() = default;
+
+    // forward and input iterator requirements
+
+    I operator->() { return inner(); }
+    I operator->() const { return inner(); }
+
+    Derived& operator++() {
+        ++inner();
+        return derived();
+    }
+
+    Derived operator++(int) {
+        Derived c(derived());
+        ++derived();
+        return c;
+    }
+
+    bool operator==(const Derived& x) const {
+        return inner()==x.inner();
+    }
+
+    bool operator!=(const Derived& x) const {
+        return !(derived()==x);
+    }
+
+    // bidirectional iterator requirements
+
+    Derived& operator--() {
+        --inner();
+        return derived();
+    }
+
+    Derived operator--(int) {
+        Derived c(derived());
+        --derived();
+        return c;
+    }
+
+    // random access iterator requirements
+
+    Derived& operator+=(difference_type n) {
+        inner() += n;
+        return derived();
+    }
+
+    Derived operator+(difference_type n) const {
+        Derived c(derived());
+        return c += n;
+    }
+
+    friend Derived operator+(difference_type n, const Derived& x) {
+        return x+n;
+    }
+
+    Derived& operator-=(difference_type n) {
+        inner() -= n;
+        return *this;
+    }
+
+    Derived operator-(difference_type n) const {
+        Derived c(derived());
+        return c -= n;
+    }
+
+    difference_type operator-(const Derived& x) const {
+        return inner()-x.inner();
+    }
+
+    bool operator<(const Derived& x) const {
+        return inner()<x.inner();
+    }
+
+    bool operator<=(const Derived& x) const {
+        return derived()<x || derived()==x;
+    }
+
+    bool operator>=(const Derived& x) const {
+        return !(derived()<x);
+    }
+
+    bool operator>(const Derived& x) const {
+        return !(derived()<=x);
+    }
+};
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/meta.hpp b/src/util/meta.hpp
index 2e22e31ceda0ff83588625486ec7a36161569733..7b1fb0d6f228175377242b607a111457ed90b414 100644
--- a/src/util/meta.hpp
+++ b/src/util/meta.hpp
@@ -2,6 +2,7 @@
 
 /* Type utilities and convenience expressions.  */
 
+#include <iterator>
 #include <type_traits>
 
 namespace nest {
@@ -13,8 +14,45 @@ namespace util {
 template <typename T>
 using result_of_t = typename std::result_of<T>::type;
 
-template <bool V>
-using enable_if_t = typename std::enable_if<V>::type;
+template <bool V, typename R = void>
+using enable_if_t = typename std::enable_if<V, R>::type;
+
+template <class...>
+using void_t = void;
+
+template <typename T>
+using decay_t = typename std::decay<T>::type;
+
+template <typename X>
+std::size_t size(const X& x) { return x.size(); }
+
+template <typename X, std::size_t N>
+constexpr std::size_t size(X (&)[N]) { return N; }
+
+template <typename T>
+constexpr auto cbegin(const T& c) -> decltype(std::begin(c)) {
+    return std::begin(c);
+}
+
+template <typename T>
+constexpr auto cend(const T& c) -> decltype(std::end(c)) {
+    return std::end(c);
+}
+
+// Types associated with a container or sequence
+
+template <typename Seq>
+struct sequence_traits {
+    using iterator = decltype(std::begin(std::declval<Seq&>()));
+    using const_iterator = decltype(cbegin(std::declval<Seq&>()));
+    using value_type = typename std::iterator_traits<iterator>::value_type;
+    using reference = typename std::iterator_traits<iterator>::reference;
+    using difference_type = typename std::iterator_traits<iterator>::difference_type;
+    using size_type = decltype(size(std::declval<Seq&>()));
+    // for use with heterogeneous ranges
+    using sentinel = decltype(std::end(std::declval<Seq&>()));
+    using const_sentinel = decltype(cend(std::declval<Seq&>()));
+};
 
 // Convenience short cuts
 
@@ -26,10 +64,91 @@ template <typename T>
 using enable_if_move_constructible_t =
     enable_if_t<std::is_move_constructible<T>::value>;
 
+template <typename T>
+using enable_if_default_constructible_t =
+    enable_if_t<std::is_default_constructible<T>::value>;
+
 template <typename... T>
 using enable_if_constructible_t =
     enable_if_t<std::is_constructible<T...>::value>;
 
+template <typename T>
+using enable_if_copy_assignable_t =
+    enable_if_t<std::is_copy_assignable<T>::value>;
+
+template <typename T>
+using enable_if_move_assignable_t =
+    enable_if_t<std::is_move_assignable<T>::value>;
+
+// Iterator class test
+// (might not be portable before C++17)
+
+template <typename T, typename = void>
+struct is_iterator: public std::false_type {};
+
+template <typename T>
+struct is_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>>:
+    public std::true_type {};
+
+template <typename T>
+using is_iterator_t = typename is_iterator<T>::type;
+
+// Random access iterator test
+
+template <typename T, typename = void>
+struct is_random_access_iterator: public std::false_type {};
+
+template <typename T>
+struct is_random_access_iterator<T, enable_if_t<
+        std::is_same<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<T>::iterator_category>::value
+    >> : public std::true_type {};
+
+template <typename T>
+using is_random_access_iterator_t = typename is_random_access_iterator<T>::type;
+
+// Bidirectional iterator test
+
+template <typename T, typename = void>
+struct is_bidirectional_iterator: public std::false_type {};
+
+template <typename T>
+struct is_bidirectional_iterator<T, enable_if_t<
+        std::is_same<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<T>::iterator_category>::value
+        ||
+        std::is_same<
+            std::bidirectional_iterator_tag,
+            typename std::iterator_traits<T>::iterator_category>::value
+    >> : public std::true_type {};
+
+template <typename T>
+using is_bidirectional_iterator_t = typename is_bidirectional_iterator<T>::type;
+
+// Forward iterator test
+
+template <typename T, typename = void>
+struct is_forward_iterator: public std::false_type {};
+
+template <typename T>
+struct is_forward_iterator<T, enable_if_t<
+        std::is_same<
+            std::random_access_iterator_tag,
+            typename std::iterator_traits<T>::iterator_category>::value
+        ||
+        std::is_same<
+            std::bidirectional_iterator_tag,
+            typename std::iterator_traits<T>::iterator_category>::value
+        ||
+        std::is_same<
+            std::forward_iterator_tag,
+            typename std::iterator_traits<T>::iterator_category>::value
+    >> : public std::true_type {};
+
+template <typename T>
+using is_forward_iterator_t = typename is_forward_iterator<T>::type;
 
 } // namespace util
 } // namespace mc
diff --git a/src/util/optional.hpp b/src/util/optional.hpp
index 01e055c1730812eb04c7b329abc80a71a21f075c..40b40d7c3ff27352638c789575172a5cd5fee8d4 100644
--- a/src/util/optional.hpp
+++ b/src/util/optional.hpp
@@ -38,16 +38,6 @@ struct optional_unset_error: std::runtime_error {
     {}
 };
 
-struct optional_invalid_dereference: std::runtime_error {
-    explicit optional_invalid_dereference(const std::string& what_str)
-        : std::runtime_error(what_str)
-    {}
-
-    optional_invalid_dereference()
-        : std::runtime_error("derefernce of optional<void> value")
-    {}
-};
-
 struct nothing_t {};
 constexpr nothing_t nothing{};
 
@@ -68,21 +58,21 @@ namespace detail {
     struct optional_tag {};
 
     template <typename X>
-    using is_optional = std::is_base_of<optional_tag, typename std::decay<X>::type>;
+    using is_optional = std::is_base_of<optional_tag, decay_t<X>>;
 
-    template <typename D,typename X>
+    template <typename D, typename X>
     struct wrapped_type_impl {
         using type = X;
     };
 
-    template <typename D,typename X>
-    struct wrapped_type_impl<optional<D>,X> {
+    template <typename D, typename X>
+    struct wrapped_type_impl<optional<D>, X> {
         using type = D;
     };
 
     template <typename X>
     struct wrapped_type {
-       using type = typename wrapped_type_impl<typename std::decay<X>::type,X>::type;
+       using type = typename wrapped_type_impl<decay_t<X>, X>::type;
     };
 
     template <typename X>
@@ -96,10 +86,10 @@ namespace detail {
         using data_type = util::uninitialized<X>;
 
     public:
-        using reference_type = typename data_type::reference_type;
-        using const_reference_type = typename data_type::const_reference_type;
-        using pointer_type = typename data_type::pointer_type;
-        using const_pointer_type = typename data_type::const_pointer_type;
+        using reference = typename data_type::reference;
+        using const_reference = typename data_type::const_reference;
+        using pointer = typename data_type::pointer;
+        using const_pointer = typename data_type::const_pointer;
 
     protected:
         bool set;
@@ -114,8 +104,8 @@ namespace detail {
             }
         }
 
-        reference_type       ref()       { return data.ref(); }
-        const_reference_type ref() const { return data.cref(); }
+        reference       ref()       { return data.ref(); }
+        const_reference ref() const { return data.cref(); }
 
     public:
         ~optional_base() {
@@ -124,28 +114,24 @@ namespace detail {
             }
         }
 
-        const_pointer_type operator->() const { return data.ptr(); }
-        pointer_type       operator->()       { return data.ptr(); }
+        pointer operator->() { return data.ptr(); }
+        const_pointer operator->() const { return data.ptr(); }
 
-        const_reference_type operator*() const { return ref(); }
-        reference_type       operator*()       { return ref(); }
+        reference operator*() { return ref(); }
+        const_reference operator*() const { return ref(); }
 
-        reference_type get() {
-            if (set) {
-                return ref();
-            }
-            else {
+        reference get() {
+            if (!set) {
                 throw optional_unset_error();
             }
+            return ref();
         }
 
-        const_reference_type get() const {
-            if (set) {
-                return ref();
-            }
-            else {
+        const_reference get() const {
+            if (!set) {
                 throw optional_unset_error();
             }
+            return ref();
         }
 
         explicit operator bool() const { return set; }
@@ -206,16 +192,16 @@ namespace detail {
     private:
         template <typename R, bool F_void_return>
         struct bind_impl {
-            template <typename DT,typename F>
-            static R bind(DT& d,F&& f) {
+            template <typename DT, typename F>
+            static R bind(DT& d, F&& f) {
                 return R(d.apply(std::forward<F>(f)));
             }
         };
 
         template <typename R>
-        struct bind_impl<R,true> {
-            template <typename DT,typename F>
-            static R bind(DT& d,F&& f) {
+        struct bind_impl<R, true> {
+            template <typename DT, typename F>
+            static R bind(DT& d, F&& f) {
                 d.apply(std::forward<F>(f));
                 return R(true);
             }
@@ -243,30 +229,27 @@ struct optional: detail::optional_base<X> {
     using base::reset;
     using base::data;
 
-    optional(): base() {}
-    optional(nothing_t): base() {}
+    optional() noexcept: base() {}
+    optional(nothing_t) noexcept: base() {}
 
-    template <
-        typename Y = X,
-        typename = enable_if_copy_constructible_t<Y>
-    >
-    optional(const X& x): base(true, x) {}
+    optional(const X& x)
+        noexcept(std::is_nothrow_copy_constructible<X>::value): base(true, x) {}
 
-    template <
-        typename Y = X,
-        typename = enable_if_move_constructible_t<Y>
-    >
-    optional(X&& x): base(true, std::move(x)) {}
+    optional(X&& x)
+        noexcept(std::is_nothrow_move_constructible<X>::value): base(true, std::move(x)) {}
 
     optional(const optional& ot): base(ot.set, ot.ref()) {}
 
     template <typename T>
-    optional(const optional<T>& ot): base(ot.set, ot.ref()) {}
+    optional(const optional<T>& ot)
+        noexcept(std::is_nothrow_constructible<X, T>::value): base(ot.set, ot.ref()) {}
 
-    optional(optional&& ot): base(ot.set, std::move(ot.ref())) {}
+    optional(optional&& ot)
+        noexcept(std::is_nothrow_move_constructible<X>::value): base(ot.set, std::move(ot.ref())) {}
 
     template <typename T>
-    optional(optional<T>&& ot): base(ot.set, std::move(ot.ref())) {}
+    optional(optional<T>&& ot)
+        noexcept(std::is_nothrow_constructible<X, T&&>::value): base(ot.set, std::move(ot.ref())) {}
 
     optional& operator=(nothing_t) {
         reset();
@@ -338,12 +321,12 @@ struct optional<X&>: detail::optional_base<X&> {
     using base::data;
     using base::reset;
 
-    optional(): base() {}
-    optional(nothing_t): base() {}
-    optional(X&x): base(true,x) {}
+    optional() noexcept: base() {}
+    optional(nothing_t) noexcept: base() {}
+    optional(X& x) noexcept: base(true, x) {}
 
     template <typename T>
-    optional(optional<T&>& ot): base(ot.set,ot.ref()) {}
+    optional(optional<T&>& ot) noexcept: base(ot.set, ot.ref()) {}
 
     optional& operator=(nothing_t) {
         reset();
@@ -380,10 +363,10 @@ struct optional<void>: detail::optional_base<void> {
     optional(): base() {}
 
     template <typename T>
-    optional(T): base(true,true) {}
+    optional(T): base(true, true) {}
 
     template <typename T>
-    optional(const optional<T>& o): base(o.set,true) {}
+    optional(const optional<T>& o): base(o.set, true) {}
 
     optional& operator=(nothing_t) {
         reset();
@@ -421,7 +404,7 @@ typename std::enable_if<
         >::type
     >
 >::type
-operator|(A&& a,B&& b) {
+operator|(A&& a, B&& b) {
     return detail::decay_bool(a) ? a : b;
 }
 
@@ -430,7 +413,7 @@ typename std::enable_if<
     detail::is_optional<A>::value || detail::is_optional<B>::value,
     optional<detail::wrapped_type_t<B>>
 >::type
-operator&(A&& a,B&& b) {
+operator&(A&& a, B&& b) {
     using result_type = optional<detail::wrapped_type_t<B>>;
     return a ? b: result_type();
 }
diff --git a/src/util/partition.hpp b/src/util/partition.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b35f81de8fa1f40217e331dd96e09a11eba1c174
--- /dev/null
+++ b/src/util/partition.hpp
@@ -0,0 +1,165 @@
+#pragma once
+
+#include <iterator>
+#include <stdexcept>
+#include <type_traits>
+
+#include <util/either.hpp>
+#include <util/meta.hpp>
+#include <util/partition_iterator.hpp>
+#include <util/range.hpp>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+struct invalid_partition: std::runtime_error {
+    explicit invalid_partition(const std::string& what): std::runtime_error(what) {}
+    explicit invalid_partition(const char* what): std::runtime_error(what) {}
+};
+
+/*
+ * Present a sequence with monotically increasing values as a partition,
+ */
+template <typename I>
+class partition_range: public range<partition_iterator<I>> {
+    using base = range<partition_iterator<I>>;
+    using inner_value_type = typename std::iterator_traits<I>::value_type;
+
+public:
+    using typename base::iterator;
+    using typename base::value_type;
+    using base::left;
+    using base::right;
+    using base::front;
+    using base::back;
+    using base::empty;
+
+    template <typename Seq>
+    partition_range(const Seq& s): base{std::begin(s), upto(std::begin(s), std::end(s))} {
+        EXPECTS(is_valid());
+    }
+
+    // explicitly check invariants
+    void validate() const {
+        auto ok = is_valid();
+        if (!ok) {
+            throw invalid_partition(ok.second());
+        }
+    }
+
+    // find half-open sub-interval containing x
+    iterator find(const inner_value_type& x) const {
+        if (empty()) {
+            return right;
+        }
+
+        auto divs = divisions();
+        auto i = std::upper_bound(divs.left, divs.right, x);
+        if (i==divs.left || i==divs.right) {
+            return right;
+        }
+        return iterator{std::prev(i)};
+    }
+
+    // access to underlying divisions
+    range<I> divisions() const {
+        return {left.get(), std::next(right.get())};
+    }
+
+    // global upper and lower bounds of partition
+    value_type bounds() const {
+        return {front().first, back().second};
+    }
+
+private:
+    either<bool, std::string> is_valid() const {
+        if (!std::is_sorted(left.get(), right.get())) {
+            return std::string("offsets are not monotonically increasing");
+        }
+        else {
+            return true;
+        }
+    }
+};
+
+
+template <
+    typename Seq,
+    typename SeqIter = typename sequence_traits<Seq>::const_iterator,
+    typename = enable_if_t<is_forward_iterator<SeqIter>::value>
+>
+partition_range<SeqIter> partition_view(const Seq& r) {
+    return partition_range<SeqIter>(r);
+}
+
+/*
+ * Construct a monotonically increasing sequence in a provided
+ * container representing a partition from a sequence of subset sizes.
+ *
+ * If the first parameter is `partition_in_place`, the provided
+ * container `divisions` will not be resized, and the partition will 
+ * be of length `util::size(divisions)-1` or zero if `divisions` is
+ * empty.
+ *
+ * Otherwise, `divisions` will be be resized to `util::size(sizes)+1`
+ * and represent a partition of length `util::size(sizes)`.
+ *
+ * Returns a partition view over `divisions`.
+ */
+
+struct partition_in_place_t {
+    constexpr partition_in_place_t() {}
+};
+
+constexpr partition_in_place_t partition_in_place;
+
+template <
+    typename Part,
+    typename Sizes,
+    typename T = typename sequence_traits<Part>::value_type
+>
+partition_range<typename sequence_traits<Part>::const_iterator>
+make_partition(partition_in_place_t, Part& divisions, const Sizes& sizes, T from=T{}) {
+    auto pi = std::begin(divisions);
+    auto pe = std::end(divisions);
+    auto si = std::begin(sizes);
+    auto se = std::end(sizes);
+
+    if (pi!=pe) {
+        *pi++ = from;
+        while (pi!=pe && si!=se) {
+            from += *si++;
+            *pi++ = from;
+        }
+        while (pi!=pe) {
+            *pi++ = from;
+        }
+    }
+    return partition_view(divisions);
+}
+
+template <
+    typename Part,
+    typename Sizes,
+    typename T = typename sequence_traits<Part>::value_type
+>
+partition_range<typename sequence_traits<Part>::const_iterator>
+make_partition(Part& divisions, const Sizes& sizes, T from=T{}) {
+    divisions.resize(size(sizes)+1);
+
+    // (would use std::inclusive_scan in C++17)
+    auto pi = std::begin(divisions);
+    for (const auto& s: sizes) {
+        *pi++ = from;
+        from += s;
+    }
+    *pi = from;
+
+    return partition_view(divisions);
+}
+
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/partition_iterator.hpp b/src/util/partition_iterator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f13b82f25d43c604680ff4e96b3efd6936e98aea
--- /dev/null
+++ b/src/util/partition_iterator.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+/*
+ * Present a monotonically increasing sequence of values given by an iterator
+ * as a sequence of pairs representing half-open intervals.
+ *
+ * Implementation is a thin wrapper over underlying iterator.
+ */
+
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <util/iterutil.hpp>
+#include <util/meta.hpp>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+template <typename I>
+class partition_iterator: public iterator_adaptor<partition_iterator<I>, I> {
+    using base = iterator_adaptor<partition_iterator<I>, I>;
+    friend class iterator_adaptor<partition_iterator<I>, I>;
+    I inner_;
+
+    // provides access to inner iterator for adaptor.
+    const I& inner() const { return inner_; }
+    I& inner() { return inner_; }
+
+    using inner_value_type = decay_t<decltype(*inner_)>;
+
+public:
+    using typename base::difference_type;
+    using value_type = std::pair<inner_value_type, inner_value_type>;
+    using pointer = const value_type*;
+    using reference = const value_type&;
+
+    template <
+        typename J,
+        typename = enable_if_t<!std::is_same<decay_t<J>, partition_iterator>::value>
+    >
+    explicit partition_iterator(J&& c): inner_{std::forward<J>(c)} {}
+
+    // forward and input iterator requirements
+
+    value_type operator*() const {
+        return {*inner_, *std::next(inner_)};
+    }
+
+    util::pointer_proxy<value_type> operator->() const {
+        return **this;
+    }
+
+    value_type operator[](difference_type n) const {
+        return *(*this+n);
+    }
+
+    // public access to inner iterator
+    const I& get() const { return inner_; }
+};
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/range.hpp b/src/util/range.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7777444d0f65d7feebc03f8f3e988a234462cdf6
--- /dev/null
+++ b/src/util/range.hpp
@@ -0,0 +1,332 @@
+#pragma once
+
+/* Present a pair of iterators as a non-owning collection.
+ *
+ * Two public member fields, `left` and `right`, describe
+ * the half-open interval [`left`, `right`).
+ *
+ * Constness of the range object only affects mutability
+ * of the iterators, and does not relate to the constness
+ * of the data to which the iterators refer.
+ *
+ * The `right` field may differ in type from the `left` field,
+ * in which case it is regarded as a sentinel type; the end of
+ * the interval is then marked by the first successor `i` of
+ * `left` that satisfies `i==right`.
+ *
+ * For an iterator `i` and sentinel `s`, it is expected that
+ * the tests `i==s` and `i!=s` are well defined, with the
+ * corresponding semantics.
+ */
+
+#include <cstddef>
+#include <iterator>
+#include <limits>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+
+#ifdef WITH_TBB
+#include <tbb/tbb_stddef.h>
+#endif
+
+#include <util/counter.hpp>
+#include <util/debug.hpp>
+#include <util/either.hpp>
+#include <util/iterutil.hpp>
+#include <util/meta.hpp>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+template <typename U, typename S = U>
+struct range {
+    using iterator = U;
+    using sentinel = S;
+    using const_iterator = iterator;
+    using difference_type = typename std::iterator_traits<iterator>::difference_type;
+    using size_type = typename std::make_unsigned<difference_type>::type;
+    using value_type = typename std::iterator_traits<iterator>::value_type;
+    using reference = typename std::iterator_traits<iterator>::reference;
+    using const_reference = const value_type&;
+
+    iterator left;
+    sentinel right;
+
+    range() = default;
+    range(const range&) = default;
+    range(range&&) = default;
+
+    template <typename U1, typename U2>
+    range(U1&& l, U2&& r):
+        left(std::forward<U1>(l)), right(std::forward<U2>(r))
+    {}
+
+    range& operator=(const range&) = default;
+    range& operator=(range&&) = default;
+
+    bool empty() const { return left==right; }
+
+    iterator begin() const { return left; }
+    const_iterator cbegin() const { return left; }
+
+    sentinel end() const { return right; }
+    sentinel cend() const { return right; }
+
+    template <typename V = iterator>
+    enable_if_t<is_forward_iterator<V>::value, size_type>
+    size() const {
+        return std::distance(begin(), end());
+    }
+
+    constexpr size_type max_size() const { return std::numeric_limits<size_type>::max(); }
+
+    void swap(range& other) {
+        std::swap(left, other.left);
+        std::swap(right, other.right);
+    }
+
+    decltype(*left) front() const { return *left; }
+
+    decltype(*left) back() const { return *upto(left, right); }
+
+    template <typename V = iterator>
+    enable_if_t<is_random_access_iterator<V>::value, decltype(*left)>
+    operator[](difference_type n) const {
+        return *std::next(begin(), n);
+    }
+
+    template <typename V = iterator>
+    enable_if_t<is_random_access_iterator<V>::value, decltype(*left)>
+    at(difference_type n) const {
+        if (size_type(n) >= size()) {
+            throw std::out_of_range("out of range in range");
+        }
+        return (*this)[n];
+    }
+
+#ifdef WITH_TBB
+    template <
+        typename V = iterator,
+        typename = enable_if_t<is_forward_iterator<V>::value>
+    >
+    range(range& r, tbb::split):
+        left(r.left), right(r.right)
+    {
+        std::advance(left, r.size()/2u);
+        r.right = left;
+    }
+
+    template <
+        typename V = iterator,
+        typename = enable_if_t<is_forward_iterator<V>::value>
+    >
+    range(range& r, tbb::proportional_split p):
+        left(r.left), right(r.right)
+    {
+        size_type i = (r.size()*p.left())/(p.left()+p.right());
+        if (i<1) {
+            i = 1;
+        }
+        std::advance(left, i);
+        r.right = left;
+    }
+
+    bool is_divisible() const {
+        return is_forward_iterator<U>::value && left != right && std::next(left) != right;
+    }
+
+    static const bool is_splittable_in_proportion() {
+        return is_forward_iterator<U>::value;
+    }
+#endif
+};
+
+template <typename U, typename V>
+range<U, V> make_range(const U& left, const V& right) {
+    return range<U, V>(left, right);
+}
+
+/*
+ * Use a proxy iterator to present a range as having the same begin and
+ * end types, for use with e.g. pre-C++17 ranged-for loops or STL
+ * algorithms.
+ */
+template <typename I, typename S>
+class sentinel_iterator {
+    nest::mc::util::either<I, S> e_;
+
+    bool is_sentinel() const { return e_.index()!=0; }
+
+    I& iter() {
+        EXPECTS(!is_sentinel());
+        return e_.template unsafe_get<0>();
+    }
+
+    const I& iter() const {
+        EXPECTS(!is_sentinel());
+        return e_.template unsafe_get<0>();
+    }
+
+    S& sentinel() {
+        EXPECTS(is_sentinel());
+        return e_.template unsafe_get<1>();
+    }
+
+    const S& sentinel() const {
+        EXPECTS(is_sentinel());
+        return e_.template unsafe_get<1>();
+    }
+
+public:
+    using difference_type = typename std::iterator_traits<I>::difference_type;
+    using value_type = typename std::iterator_traits<I>::value_type;
+    using pointer = typename std::iterator_traits<I>::pointer;
+    using reference = typename std::iterator_traits<I>::reference;
+    using iterator_category = typename std::iterator_traits<I>::iterator_category;
+
+    sentinel_iterator(I i): e_(i) {}
+
+    template <typename V = S, typename = enable_if_t<!std::is_same<I, V>::value>>
+    sentinel_iterator(S i): e_(i) {}
+
+    sentinel_iterator() = default;
+    sentinel_iterator(const sentinel_iterator&) = default;
+    sentinel_iterator(sentinel_iterator&&) = default;
+
+    sentinel_iterator& operator=(const sentinel_iterator&) = default;
+    sentinel_iterator& operator=(sentinel_iterator&&) = default;
+
+    // forward and input iterator requirements
+
+    auto operator*() const -> decltype(*iter()) { return *iter(); }
+
+    I operator->() const { return e_.template ptr<0>(); }
+
+    sentinel_iterator& operator++() {
+        ++iter();
+        return *this;
+    }
+
+    sentinel_iterator operator++(int) {
+        sentinel_iterator c(*this);
+        ++*this;
+        return c;
+    }
+
+    bool operator==(const sentinel_iterator& x) const {
+        if (is_sentinel()) {
+            return x.is_sentinel() || x.iter()==sentinel();
+        }
+        else {
+            return x.is_sentinel()? iter()==x.sentinel(): iter()==x.iter();
+        }
+    }
+
+    bool operator!=(const sentinel_iterator& x) const {
+        return !(*this==x);
+    }
+
+    // bidirectional iterator requirements
+
+    sentinel_iterator& operator--() {
+        --iter();
+        return *this;
+    }
+
+    sentinel_iterator operator--(int) {
+        sentinel_iterator c(*this);
+        --*this;
+        return c;
+    }
+
+    // random access iterator requirements
+
+    sentinel_iterator &operator+=(difference_type n) {
+        iter() += n;
+        return *this;
+    }
+
+    sentinel_iterator operator+(difference_type n) const {
+        sentinel_iterator c(*this);
+        return c += n;
+    }
+
+    friend sentinel_iterator operator+(difference_type n, sentinel_iterator x) {
+        return x+n;
+    }
+
+    sentinel_iterator& operator-=(difference_type n) {
+        iter() -= n;
+        return *this;
+    }
+
+    sentinel_iterator operator-(difference_type n) const {
+        sentinel_iterator c(*this);
+        return c -= n;
+    }
+
+    difference_type operator-(sentinel_iterator x) const {
+        return iter()-x.iter();
+    }
+
+    auto operator[](difference_type n) const -> decltype(*iter()){
+        return *(iter()+n);
+    }
+
+    bool operator<=(const sentinel_iterator& x) const {
+        return x.is_sentinel() || (!is_sentinel() && iter()<=x.iter());
+    }
+
+    bool operator<(const sentinel_iterator& x) const {
+        return !is_sentinel() && (x.is_sentinel() || iter()<=x.iter());
+    }
+
+    bool operator>=(const sentinel_iterator& x) const {
+        return !(x<*this);
+    }
+
+    bool operator>(const sentinel_iterator& x) const {
+        return !(x<=*this);
+    }
+};
+
+template <typename I, typename S>
+using sentinel_iterator_t =
+    typename std::conditional<std::is_same<I, S>::value, I, sentinel_iterator<I, S>>::type;
+
+template <typename I, typename S>
+sentinel_iterator_t<I, S> make_sentinel_iterator(const I& i, const S& s) {
+    return sentinel_iterator_t<I, S>(i);
+}
+
+template <typename I, typename S>
+sentinel_iterator_t<I, S> make_sentinel_end(const I& i, const S& s) {
+    return sentinel_iterator_t<I, S>(s);
+}
+
+template <typename Seq>
+auto canonical_view(const Seq& s) ->
+    range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>>
+{
+    return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))};
+}
+
+/*
+ * Present a single item as a range
+ */
+
+template <typename T>
+range<T*> singleton_view(T& item) {
+    return {&item, &item+1};
+}
+
+template <typename T>
+range<const T*> singleton_view(const T& item) {
+    return {&item, &item+1};
+}
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/span.hpp b/src/util/span.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..06cdfa36924324c589af8a16412d8c167fb0429f
--- /dev/null
+++ b/src/util/span.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+/* 
+ * Presents a half-open interval [a,b) of integral values as a container.
+ */
+
+#include <type_traits>
+#include <utility>
+
+#include <util/counter.hpp>
+#include <util/range.hpp>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+template <typename I>
+using span = range<counter<I>>;
+
+template <typename I, typename J>
+span<typename std::common_type<I, J>::type> make_span(I left, J right) {
+    return span<typename std::common_type<I, J>::type>(left, right);
+}
+
+template <typename I, typename J>
+span<typename std::common_type<I, J>::type> make_span(std::pair<I, J> interval) {
+    return span<typename std::common_type<I, J>::type>(interval.first, interval.second);
+}
+
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/transform.hpp b/src/util/transform.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f7d9fed04c721f09611757c84c54e5f13cd15942
--- /dev/null
+++ b/src/util/transform.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+/*
+ * An iterator adaptor that presents the values from an underlying
+ * iterator after applying a provided functor.
+ */
+
+#include <iterator>
+#include <memory>
+#include <type_traits>
+
+#include <util/iterutil.hpp>
+#include <util/meta.hpp>
+#include <util/range.hpp>
+
+namespace nest {
+namespace mc {
+namespace util {
+
+template <typename I, typename F>
+class transform_iterator: public iterator_adaptor<transform_iterator<I, F>, I> {
+    using base = iterator_adaptor<transform_iterator<I, F>, I>;
+    friend class iterator_adaptor<transform_iterator<I, F>, I>;
+
+    I inner_;
+    F f_;
+
+    // provides access to inner iterator for adaptor.
+    const I& inner() const { return inner_; }
+    I& inner() { return inner_; }
+
+    using inner_value_type = util::decay_t<decltype(*inner_)>;
+
+public:
+    using typename base::difference_type;
+    using value_type = decltype(f_(*inner_));
+    using pointer = const value_type*;
+    using reference = const value_type&;
+
+    template <typename J, typename G>
+    transform_iterator(J&& c, G&& g): inner_{std::forward<J>(c)}, f_{std::forward<G>(g)} {}
+
+    transform_iterator(const transform_iterator&) = default;
+    transform_iterator(transform_iterator&&) = default;
+    transform_iterator& operator=(const transform_iterator&) = default;
+    transform_iterator& operator=(transform_iterator&&) = default;
+
+    // forward and input iterator requirements
+
+    value_type operator*() const {
+        return f_(*inner_);
+    }
+
+    util::pointer_proxy<value_type> operator->() const {
+        return **this;
+    }
+
+    value_type operator[](difference_type n) const {
+        return *(*this+n);
+    }
+
+    // public access to inner iterator
+    const I& get() const { return inner_; }
+
+    bool operator==(const transform_iterator& x) const { return inner_==x.inner_; }
+    bool operator!=(const transform_iterator& x) const { return inner_!=x.inner_; }
+
+    // expose inner iterator for testing against a sentinel
+    template <typename Sentinel>
+    bool operator==(const Sentinel& s) const { return inner_==s; }
+
+    template <typename Sentinel>
+    bool operator!=(const Sentinel& s) const { return !(inner_==s); }
+};
+
+template <typename I, typename F>
+transform_iterator<I, util::decay_t<F>> make_transform_iterator(const I& i, const F& f) {
+    return transform_iterator<I, util::decay_t<F>>(i, f);
+}
+
+template <
+    typename Seq,
+    typename F,
+    typename seq_citer = typename sequence_traits<Seq>::const_iterator,
+    typename seq_csent = typename sequence_traits<Seq>::const_sentinel,
+    typename = enable_if_t<std::is_same<seq_citer, seq_csent>::value>
+>
+range<transform_iterator<seq_citer, util::decay_t<F>>>
+transform_view(const Seq& s, const F& f) {
+    return {make_transform_iterator(cbegin(s), f), make_transform_iterator(cend(s), f)};
+}
+
+
+template <
+    typename Seq,
+    typename F,
+    typename seq_citer = typename sequence_traits<Seq>::const_iterator,
+    typename seq_csent = typename sequence_traits<Seq>::const_sentinel,
+    typename = enable_if_t<!std::is_same<seq_citer, seq_csent>::value>
+>
+range<transform_iterator<seq_citer, util::decay_t<F>>, seq_csent>
+transform_view(const Seq& s, const F& f) {
+    return {make_transform_iterator(cbegin(s), f), cend(s)};
+}
+
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/src/util/uninitialized.hpp b/src/util/uninitialized.hpp
index 846e46d5a2451fe1535f026ee4b801e5303a6f0c..8e3613cd6b342b40c7f2cd76edfd4e7acec5c582 100644
--- a/src/util/uninitialized.hpp
+++ b/src/util/uninitialized.hpp
@@ -18,25 +18,26 @@ namespace nest {
 namespace mc {
 namespace util {
 
-/* Maintains storage for a value of type X, with explicit
+/*
+ * Maintains storage for a value of type X, with explicit
  * construction and destruction.
  */
 template <typename X>
-struct uninitialized {
+class uninitialized {
 private:
     typename std::aligned_storage<sizeof(X), alignof(X)>::type data;
 
 public:
-    using pointer_type = X*;
-    using const_pointer_type = const X*;
-    using reference_type = X&;
-    using const_reference_type = const X&;
+    using pointer = X*;
+    using const_pointer = const X*;
+    using reference = X&;
+    using const_reference= const X&;
 
-    pointer_type ptr() { return reinterpret_cast<X*>(&data); }
-    const_pointer_type cptr() const { return reinterpret_cast<const X*>(&data); }
+    pointer ptr() { return reinterpret_cast<X*>(&data); }
+    const_pointer cptr() const { return reinterpret_cast<const X*>(&data); }
 
-    reference_type ref() { return *reinterpret_cast<X*>(&data); }
-    const_reference_type cref() const { return *reinterpret_cast<const X*>(&data); }
+    reference ref() { return *reinterpret_cast<X*>(&data); }
+    const_reference cref() const { return *reinterpret_cast<const X*>(&data); }
 
     // Copy construct the value.
     template <
@@ -60,45 +61,46 @@ public:
 
     // Apply the one-parameter functor F to the value by reference.
     template <typename F>
-    result_of_t<F(reference_type)> apply(F&& f) { return f(ref()); }
+    result_of_t<F(reference)> apply(F&& f) { return f(ref()); }
 
     // Apply the one-parameter functor F to the value by const reference.
     template <typename F>
-    result_of_t<F(const_reference_type)> apply(F&& f) const { return f(cref()); }
+    result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); }
 };
 
-/* Maintains storage for a pointer of type X, representing
+/*
+ * Maintains storage for a pointer of type X, representing
  * a possibly uninitialized reference.
  */
 template <typename X>
-struct uninitialized<X&> {
+class uninitialized<X&> {
 private:
     X *data;
 
 public:
-    using pointer_type = X*;
-    using const_pointer_type = const X*;
-    using reference_type = X&;
-    using const_reference_type = const X&;
+    using pointer = X*;
+    using const_pointer = const X*;
+    using reference = X&;
+    using const_reference = const X&;
 
-    pointer_type ptr() { return data; }
-    const_pointer_type cptr() const { return data; }
+    pointer ptr() { return data; }
+    const_pointer cptr() const { return data; }
 
-    reference_type ref() { return *data; }
-    const_reference_type cref() const { return *data; }
+    reference ref() { return *data; }
+    const_reference cref() const { return *data; }
 
     void construct(X& x) { data = &x; }
     void destruct() {}
 
     // Apply the one-parameter functor F to the value by reference.
     template <typename F>
-    result_of_t<F(reference_type)> apply(F&& f) {
+    result_of_t<F(reference)> apply(F&& f) {
         return f(ref());
     }
 
     // Apply the one-parameter functor F to the value by const reference.
     template <typename F>
-    result_of_t<F(const_reference_type)> apply(F&& f) const {
+    result_of_t<F(const_reference)> apply(F&& f) const {
         return f(cref());
     }
 };
@@ -108,17 +110,18 @@ public:
  * Allows the use of uninitialized<X> for void X, for generic applications.
  */
 template <>
-struct uninitialized<void> {
-    using pointer_type = void*;
-    using const_pointer_type = const void*;
-    using reference_type = void;
-    using const_reference_type = void;
+class uninitialized<void> {
+public:
+    using pointer = void*;
+    using const_pointer = const void*;
+    using reference = void;
+    using const_reference = void;
 
-    pointer_type ptr() { return nullptr; }
-    const_pointer_type cptr() const { return nullptr; }
+    pointer ptr() { return nullptr; }
+    const_pointer cptr() const { return nullptr; }
 
-    reference_type ref() {}
-    const_reference_type cref() const {}
+    reference ref() {}
+    const_reference cref() const {}
 
     // No operation.
     void construct(...) {}
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 83c537b26385b62ece833d59931ece57bc1cd8cc..6b34d70125bb4d9792f8abb6b68daf411dfe78d8 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -14,6 +14,8 @@ set(TEST_SOURCES
     test_double_buffer.cpp
     test_cell.cpp
     test_compartments.cpp
+    test_counter.cpp
+    test_either.cpp
     test_event_queue.cpp
     test_fvm.cpp
     test_cell_group.cpp
@@ -24,15 +26,19 @@ set(TEST_SOURCES
     test_nop.cpp
     test_optional.cpp
     test_parameters.cpp
+    test_partition.cpp
     test_point.cpp
     test_probe.cpp
     test_segment.cpp
+    test_range.cpp
+    test_span.cpp
     test_spikes.cpp
     test_spike_store.cpp
     test_stimulus.cpp
     test_swcio.cpp
     test_synapses.cpp
     test_tree.cpp
+    test_transform.cpp
     test_uninitialized.cpp
 
     # unit test driver
diff --git a/tests/unit/test_counter.cpp b/tests/unit/test_counter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7c97f2277e08a865ebb0be0a7e7b9d4db18f7360
--- /dev/null
+++ b/tests/unit/test_counter.cpp
@@ -0,0 +1,131 @@
+#include "gtest.h"
+
+#include <iterator>
+#include <type_traits>
+
+#include <util/counter.hpp>
+
+using namespace nest::mc;
+
+template <typename V>
+class counter_test: public ::testing::Test {};
+
+TYPED_TEST_CASE_P(counter_test);
+
+TYPED_TEST_P(counter_test, value) {
+    using int_type = TypeParam;
+    using counter = util::counter<int_type>;
+
+    counter c0;
+    EXPECT_EQ(int_type{0}, *c0);
+
+    counter c1{int_type{1}};
+    counter c2{int_type{2}};
+
+    EXPECT_EQ(int_type{1}, *c1);
+    EXPECT_EQ(int_type{2}, *c2);
+
+    c2 = c1;
+    EXPECT_EQ(int_type{1}, *c2);
+
+    c2 = int_type{2};
+    EXPECT_EQ(int_type{2}, *c2);
+}
+
+TYPED_TEST_P(counter_test, compare) {
+    using int_type = TypeParam;
+    using counter = util::counter<int_type>;
+
+    counter c1{int_type{1}};
+    counter c2{int_type{2}};
+
+    EXPECT_LT(c1, c2);
+    EXPECT_LE(c1, c2);
+    EXPECT_NE(c1, c2);
+    EXPECT_GE(c2, c1);
+    EXPECT_GT(c2, c1);
+
+    counter c1bis{int_type{1}};
+
+    EXPECT_LE(c1, c1bis);
+    EXPECT_EQ(c1, c1bis);
+    EXPECT_GE(c1, c1bis);
+}
+
+TYPED_TEST_P(counter_test, arithmetic) {
+    using int_type = TypeParam;
+    using counter = util::counter<int_type>;
+
+    counter c1{int_type{1}};
+    counter c2{int_type{10}};
+    int_type nine{9};
+
+    EXPECT_EQ(nine, c2-c1);
+    EXPECT_EQ(c2, c1+nine);
+    EXPECT_EQ(c2, nine+c1);
+    EXPECT_EQ(c1, c2-nine);
+
+    counter c3 = c1;
+    counter c4 = (c3 += nine);
+
+    EXPECT_EQ(c2, c3);
+    EXPECT_EQ(c3, c4);
+
+    c3 = c2;
+    c4 = (c3 -= nine);
+
+    EXPECT_EQ(c1, c3);
+    EXPECT_EQ(c3, c4);
+
+    c3 = c1;
+    EXPECT_EQ(counter{2}, ++c3);
+    EXPECT_EQ(counter{3}, ++c3);
+    EXPECT_EQ(counter{2}, --c3);
+    EXPECT_EQ(counter{1}, --c3);
+
+    c3 = c1;
+    EXPECT_EQ(counter{1}, c3++);
+    EXPECT_EQ(counter{2}, c3++);
+    EXPECT_EQ(counter{3}, c3--);
+    EXPECT_EQ(counter{2}, c3--);
+
+    EXPECT_EQ(int_type{10}, c2[0]);
+    EXPECT_EQ(int_type{4},  c2[-6]);
+    EXPECT_EQ(int_type{19}, c2[9]);
+}
+
+TYPED_TEST_P(counter_test, iterator_traits) {
+    using int_type = TypeParam;
+    using counter = util::counter<int_type>;
+    using traits = std::iterator_traits<counter>;
+
+    typename traits::reference r = *counter{int_type{3}};
+    EXPECT_EQ(r, int_type{3});
+
+    typename traits::difference_type d = counter{int_type{4}} - counter{int_type{7}};
+    EXPECT_EQ(typename traits::difference_type(-3), d);
+
+    EXPECT_TRUE((std::is_same<std::random_access_iterator_tag, typename traits::iterator_category>::value));
+}
+
+TYPED_TEST_P(counter_test, iterator_functions) {
+    using int_type = TypeParam;
+    using counter = util::counter<int_type>;
+
+    counter c1{int_type{1}};
+    counter c2{int_type{10}};
+
+    EXPECT_EQ(int_type{9}, std::distance(c1,c2));
+    counter c3{c1};
+    std::advance(c3, int_type{9});
+    EXPECT_EQ(c2, c3);
+
+    EXPECT_EQ(counter{int_type{2}}, std::next(c1));
+    EXPECT_EQ(counter{int_type{9}}, std::prev(c2));
+}
+
+REGISTER_TYPED_TEST_CASE_P(counter_test, value, compare, arithmetic, iterator_traits, iterator_functions);
+
+using int_types = ::testing::Types<signed char, unsigned char, short, unsigned short, int, unsigned, std::size_t, std::ptrdiff_t>;
+INSTANTIATE_TYPED_TEST_CASE_P(int_types, counter_test, int_types);
+
diff --git a/tests/unit/test_either.cpp b/tests/unit/test_either.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d185d8bf5eea7c7f5d0e75395d6f7e6d0f46f53
--- /dev/null
+++ b/tests/unit/test_either.cpp
@@ -0,0 +1,64 @@
+#include <typeinfo>
+#include <array>
+#include <algorithm>
+
+#include "gtest.h"
+#include "util/either.hpp"
+
+// TODO: coverage!
+
+using namespace nest::mc::util;
+
+TEST(either, basic) {
+    either<int, std::string> e0(17);
+
+    EXPECT_TRUE(e0);
+    EXPECT_EQ(17, e0.get<0>());
+    EXPECT_EQ(e0.unsafe_get<0>(), e0.get<0>());
+    EXPECT_EQ(e0.unsafe_get<0>(), e0.first());
+    EXPECT_THROW(e0.get<1>(), either_invalid_access);
+    either<int, std::string> e1("seventeen");
+
+    EXPECT_FALSE(e1);
+    EXPECT_EQ("seventeen", e1.get<1>());
+    EXPECT_EQ(e1.unsafe_get<1>(), e1.get<1>());
+    EXPECT_EQ(e1.unsafe_get<1>(), e1.second());
+    EXPECT_THROW(e1.get<0>(), either_invalid_access);
+
+    e0 = e1;
+    EXPECT_EQ("seventeen", e0.get<1>());
+    EXPECT_THROW(e0.get<0>(), either_invalid_access);
+
+    e0 = 19;
+    EXPECT_EQ(19, e0.get<0>());
+}
+
+struct no_copy {
+    int value;
+
+    no_copy(): value(23) {}
+    explicit no_copy(int v): value(v) {}
+    no_copy(const no_copy&) = delete;
+    no_copy(no_copy&&) = default;
+
+    no_copy& operator=(const no_copy&) = delete;
+    no_copy& operator=(no_copy&&) = default;
+};
+
+TEST(either, no_copy) {
+    either<no_copy, std::string> e0(no_copy{17});
+
+    EXPECT_TRUE(e0);
+
+    either<no_copy, std::string> e1(std::move(e0));
+
+    EXPECT_TRUE(e1);
+
+    either<no_copy, std::string> e2;
+    EXPECT_TRUE(e2);
+    EXPECT_EQ(23, e2.get<0>().value);
+
+    e2 = std::move(e1);
+    EXPECT_TRUE(e2);
+    EXPECT_EQ(17, e2.get<0>().value);
+}
diff --git a/tests/unit/test_partition.cpp b/tests/unit/test_partition.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..edfb95d1021ea452c1701d9a84ef2341ed17e48f
--- /dev/null
+++ b/tests/unit/test_partition.cpp
@@ -0,0 +1,150 @@
+#include "gtest.h"
+
+#include <array>
+#include <forward_list>
+#include <string>
+#include <vector>
+
+#include <util/debug.hpp>
+#include <util/nop.hpp>
+#include <util/partition.hpp>
+
+using namespace nest::mc;
+
+TEST(partition, partition_view) {
+    std::forward_list<int> fl = {1, 4, 6, 8, 10 };
+
+    auto p1 = util::partition_view(fl);
+    EXPECT_EQ(std::make_pair(1,4), p1.front());
+    EXPECT_EQ(std::make_pair(8,10), p1.back());
+    EXPECT_EQ(std::make_pair(1,10), p1.bounds());
+    EXPECT_EQ(4u, p1.size());
+
+    std::vector<double> v = {2.0, 3.6, 7.5};
+
+    auto p2 = util::partition_view(v);
+    EXPECT_EQ(2u, p2.size());
+
+    std::vector<double> ends;
+    std::vector<double> ends_expected = { 2.0, 3.6, 3.6, 7.5 };
+    for (auto b: p2) {
+        ends.push_back(b.first);
+        ends.push_back(b.second);
+    }
+    EXPECT_EQ(ends_expected, ends);
+}
+
+TEST(partition, short_partition_view) {
+    int two_divs[] = {10, 15};
+    EXPECT_EQ(1u, util::partition_view(two_divs).size());
+
+    int one_div[] = {10};
+    EXPECT_EQ(0u, util::partition_view(one_div).size());
+
+    std::array<int, 0> zero_divs;
+    EXPECT_EQ(0u, util::partition_view(zero_divs).size());
+}
+
+TEST(partition, check_monotonicity) {
+    // override any EXPECTS checks in partition
+    util::global_failed_assertion_handler = util::ignore_failed_assertion;
+
+    int divs_ok[] = {1, 2, 2, 3, 3};
+    EXPECT_NO_THROW(util::partition_view(divs_ok).validate());
+
+    int divs_bad[] = {3, 2, 1};
+    EXPECT_THROW(util::partition_view(divs_bad).validate(), util::invalid_partition);
+}
+
+TEST(partition, partition_view_find) {
+    std::vector<double> divs = { 1, 2.5, 3, 5.5 };
+    double eps = 0.1;
+    auto p = util::partition_view(divs);
+
+    EXPECT_EQ(p.end(), p.find(divs.front()-eps));
+    EXPECT_NE(p.end(), p.find(divs.front()));
+    EXPECT_EQ(divs.front(), p.find(divs.front())->first);
+
+    EXPECT_NE(p.end(), p.find(divs.back()-eps));
+    EXPECT_EQ(divs.back(), p.find(divs.back()-eps)->second);
+    EXPECT_EQ(p.end(), p.find(divs.back()));
+    EXPECT_EQ(p.end(), p.find(divs.back()+eps));
+
+    EXPECT_EQ(divs[1], p.find(divs[1]+eps)->first);
+    EXPECT_EQ(divs[2], p.find(divs[1]+eps)->second);
+}
+
+TEST(partition, partition_view_non_numeric) {
+    std::string divs[] = { "a", "dictionary", "of", "sorted", "words" };
+    auto p = util::partition_view(divs);
+
+    EXPECT_EQ("dictionary", p.find("elephant")->first);
+}
+
+TEST(partition, make_partition_in_place) {
+    unsigned sizes[] = { 7, 3, 0, 2 };
+    unsigned part_store[util::size(sizes)+1];
+
+    auto p = util::make_partition(util::partition_in_place, part_store, sizes, 10u);
+    ASSERT_EQ(4u, p.size());
+    EXPECT_EQ(std::make_pair(10u, 17u), p[0]);
+    EXPECT_EQ(std::make_pair(17u, 20u), p[1]);
+    EXPECT_EQ(std::make_pair(20u, 20u), p[2]);
+    EXPECT_EQ(std::make_pair(20u, 22u), p[3]);
+
+    // with short sizes sequence
+    unsigned short_sizes[] = { 1, 2 };
+    p = util::make_partition(util::partition_in_place, part_store, short_sizes, 0u);
+    ASSERT_EQ(4u, p.size());
+    EXPECT_EQ(std::make_pair(0u, 1u), p[0]);
+    EXPECT_EQ(std::make_pair(1u, 3u), p[1]);
+    EXPECT_EQ(std::make_pair(3u, 3u), p[2]);
+    EXPECT_EQ(std::make_pair(3u, 3u), p[3]);
+
+    // with longer sizes sequence
+    unsigned long_sizes[] = {1, 2, 3, 4, 5, 6};
+    p = util::make_partition(util::partition_in_place, part_store, long_sizes, 0u);
+    ASSERT_EQ(4u, p.size());
+    EXPECT_EQ(std::make_pair(0u, 1u), p[0]);
+    EXPECT_EQ(std::make_pair(1u, 3u), p[1]);
+    EXPECT_EQ(std::make_pair(3u, 6u), p[2]);
+    EXPECT_EQ(std::make_pair(6u, 10u), p[3]);
+
+    // with empty sizes sequence
+    std::array<unsigned, 0> no_sizes;
+    p = util::make_partition(util::partition_in_place, part_store, no_sizes, 17u);
+    ASSERT_EQ(4u, p.size());
+    EXPECT_EQ(std::make_pair(17u, 17u), p[0]);
+    EXPECT_EQ(std::make_pair(17u, 17u), p[1]);
+    EXPECT_EQ(std::make_pair(17u, 17u), p[2]);
+    EXPECT_EQ(std::make_pair(17u, 17u), p[3]);
+
+    // with short partition containers
+    unsigned part_store_one[1];
+    p = util::make_partition(util::partition_in_place, part_store_one, sizes, 10u);
+    ASSERT_EQ(0u, p.size());
+    ASSERT_TRUE(p.empty());
+
+    std::array<unsigned,0> part_store_zero;
+    p = util::make_partition(util::partition_in_place, part_store_zero, sizes, 10u);
+    ASSERT_EQ(0u, p.size());
+    ASSERT_TRUE(p.empty());
+}
+
+TEST(partition, make_partition) {
+    // (also tests differing types for sizes and divisiosn)
+    unsigned sizes[] = { 7, 3, 0, 2 };
+    std::forward_list<double> part_store = { 100.3 };
+
+    auto p = util::make_partition(part_store, sizes, 10.0);
+    ASSERT_EQ(4u, p.size());
+
+    auto pi = p.begin();
+    EXPECT_EQ(10.0, pi++->first);
+    EXPECT_EQ(17.0, pi++->first);
+    EXPECT_EQ(20.0, pi++->first);
+    EXPECT_EQ(20.0, pi->first);
+    EXPECT_EQ(22.0, pi->second);
+
+    EXPECT_EQ(p.end(), ++pi);
+}
diff --git a/tests/unit/test_range.cpp b/tests/unit/test_range.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7847212e3f0b0546c1f974dbdebab3303a15ea1d
--- /dev/null
+++ b/tests/unit/test_range.cpp
@@ -0,0 +1,171 @@
+#include "gtest.h"
+
+#include <algorithm>
+#include <iterator>
+#include <sstream>
+#include <list>
+#include <numeric>
+#include <type_traits>
+
+#ifdef WITH_TBB
+#include <tbb/tbb_stddef.h>
+#endif
+
+#include <util/range.hpp>
+
+using namespace nest::mc;
+
+TEST(range, list_iterator) {
+    std::list<int> l = { 2, 4, 6, 8, 10 };
+
+    auto s = util::make_range(l.begin(), l.end());
+
+    EXPECT_EQ(s.left, l.begin());
+    EXPECT_EQ(s.right, l.end());
+
+    EXPECT_EQ(s.begin(), l.begin());
+    EXPECT_EQ(s.end(), l.end());
+
+    EXPECT_EQ(s.size(), l.size());
+    EXPECT_EQ(s.front(), *l.begin());
+    EXPECT_EQ(s.back(), *std::prev(l.end()));
+
+    int check = std::accumulate(l.begin(), l.end(), 0);
+    int sum = 0;
+    for (auto i: s) {
+        sum += i;
+    }
+
+    EXPECT_EQ(check, sum);
+
+    auto sum2 = std::accumulate(s.begin(), s.end(), 0);
+    EXPECT_EQ(check, sum2);
+}
+
+TEST(range, pointer) {
+    int xs[] = { 10, 11, 12, 13, 14, 15, 16 };
+    int l = 2;
+    int r = 5;
+
+    util::range<int *> s(&xs[l], &xs[r]);
+    auto s_deduced = util::make_range(xs+l, xs+r);
+
+    EXPECT_TRUE((std::is_same<decltype(s), decltype(s_deduced)>::value));
+    EXPECT_EQ(s.left, s_deduced.left);
+    EXPECT_EQ(s.right, s_deduced.right);
+
+    EXPECT_EQ(3u, s.size());
+
+    EXPECT_EQ(xs[l], *s.left);
+    EXPECT_EQ(xs[l], *s.begin());
+    EXPECT_EQ(xs[l], s[0]);
+    EXPECT_EQ(xs[l], s.front());
+
+    EXPECT_EQ(xs[r], *s.right);
+    EXPECT_EQ(xs[r], *s.end());
+    EXPECT_THROW(s.at(r-l), std::out_of_range);
+
+    EXPECT_EQ(r-l, std::distance(s.begin(), s.end()));
+
+    EXPECT_TRUE(std::equal(s.begin(), s.end(), &xs[l]));
+}
+
+TEST(range, input_iterator) {
+    int nums[] = { 10, 9, 8, 7, 6 };
+    std::istringstream sin("10 9 8 7 6");
+    auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>());
+
+    EXPECT_TRUE(std::equal(s.begin(), s.end(), &nums[0]));
+}
+
+TEST(range, const_iterator) {
+    std::vector<int> xs = { 1, 2, 3, 4, 5 };
+    auto r = util::make_range(xs.begin(), xs.end());
+    EXPECT_TRUE((std::is_same<int&, decltype(r.front())>::value));
+
+    const auto& xs_const = xs;
+    auto r_const = util::make_range(xs_const.begin(), xs_const.end());
+    EXPECT_TRUE((std::is_same<const int&, decltype(r_const.front())>::value));
+}
+
+struct null_terminated_t {
+    bool operator==(const char *p) const { return !*p; }
+    bool operator!=(const char *p) const { return !!*p; }
+
+    friend bool operator==(const char *p, null_terminated_t x) {
+        return x==p;
+    }
+
+    friend bool operator!=(const char *p, null_terminated_t x) {
+        return x!=p;
+    }
+
+    constexpr null_terminated_t() {}
+};
+
+constexpr null_terminated_t null_terminated;
+
+TEST(range, sentinel) {
+    const char *cstr = "hello world";
+    std::string s;
+
+    auto cstr_range = util::make_range(cstr, null_terminated);
+    for (auto i=cstr_range.begin(); i!=cstr_range.end(); ++i) {
+        s += *i;
+    }
+
+    EXPECT_EQ(s, std::string(cstr));
+
+    s.clear();
+    for (auto c: canonical_view(cstr_range)) {
+        s += c;
+    }
+
+    EXPECT_EQ(s, std::string(cstr));
+}
+
+#ifdef WITH_TBB
+
+TEST(range, tbb_split) {
+    constexpr std::size_t N = 20;
+    int xs[N];
+
+    for (unsigned i = 0; i<N; ++i) {
+        xs[i] = i;
+    }
+
+    auto s = util::make_range(&xs[0], &xs[0]+N);
+
+    while (s.size()>1) {
+        auto ssize = s.size();
+        auto r = decltype(s){s, tbb::split{}};
+        EXPECT_GT(r.size(), 0u);
+        EXPECT_GT(s.size(), 0u);
+        EXPECT_EQ(ssize, r.size()+s.size());
+        EXPECT_EQ(s.end(), r.begin());
+
+        EXPECT_TRUE(r.size()>1 || !r.is_divisible());
+        EXPECT_TRUE(s.size()>1 || !s.is_divisible());
+    }
+
+    for (unsigned i = 1; i<N-1; ++i) {
+        s = util::make_range(&xs[0], &xs[0]+N);
+        // expect exact splitting by proportion in this instance
+
+        auto r = decltype(s){s, tbb::proportional_split{i, N-i}};
+        EXPECT_EQ(&xs[0], s.left);
+        EXPECT_EQ(&xs[0]+i, s.right);
+        EXPECT_EQ(&xs[0]+i, r.left);
+        EXPECT_EQ(&xs[0]+N, r.right);
+    }
+}
+
+TEST(range, tbb_no_split) {
+    std::istringstream sin("10 9 8 7 6");
+    auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>());
+
+    EXPECT_FALSE(decltype(s)::is_splittable_in_proportion());
+    EXPECT_FALSE(s.is_divisible());
+}
+
+#endif
diff --git a/tests/unit/test_span.cpp b/tests/unit/test_span.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..edb55f7d65e7f0321add82de6e0879143050fede
--- /dev/null
+++ b/tests/unit/test_span.cpp
@@ -0,0 +1,93 @@
+#include "gtest.h"
+
+#include <algorithm>
+#include <iterator>
+#include <list>
+#include <numeric>
+#include <type_traits>
+#include <utility>
+
+#include <util/span.hpp>
+
+using namespace nest::mc;
+
+TEST(span, int_access) {
+    using span = util::span<int>;
+
+    int n = 97;
+    int a = 3;
+    int b = a+n;
+
+    span s(a, b);
+    EXPECT_EQ(s.left, a);
+    EXPECT_EQ(s.right, b);
+
+    EXPECT_EQ(s.size(), std::size_t(n));
+
+    EXPECT_EQ(s.front(), a);
+    EXPECT_EQ(s.back(), b-1);
+
+    EXPECT_EQ(s[0], a);
+    EXPECT_EQ(s[1], a+1);
+    EXPECT_EQ(s[n-1], b-1);
+
+    EXPECT_NO_THROW(s.at(0));
+    EXPECT_NO_THROW(s.at(n-1));
+    EXPECT_THROW(s.at(n), std::out_of_range);
+    EXPECT_THROW(s.at(n+1), std::out_of_range);
+    EXPECT_THROW(s.at(-1), std::out_of_range);
+}
+
+TEST(span, int_iterators) {
+    using span = util::span<int>;
+
+    int n = 97;
+    int a = 3;
+    int b = a+n;
+
+    span s(a, b);
+
+    EXPECT_TRUE(util::is_iterator<span::iterator>::value);
+    EXPECT_TRUE(util::is_random_access_iterator<span::iterator>::value);
+
+    EXPECT_EQ(n, std::distance(s.begin(), s.end()));
+    EXPECT_EQ(n, std::distance(s.cbegin(), s.cend()));
+
+    int sum = 0;
+    for (auto i: span(a, b)) {
+        sum += i;
+    }
+    EXPECT_EQ(sum, (a+b-1)*(b-a)/2);
+}
+
+TEST(span, make_span) {
+    auto s_empty = util::make_span(3, 3);
+    EXPECT_TRUE(s_empty.empty());
+
+    {
+        auto s = util::make_span((short)3, (unsigned long long)10);
+        auto first = s.front();
+        auto last = s.back();
+
+        EXPECT_EQ(3u, first);
+        EXPECT_EQ(9u, last);
+
+        EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value));
+        EXPECT_TRUE((std::is_same<unsigned long long, decltype(first)>::value));
+    }
+
+    {
+        // type abuse! should promote bool to long in span.
+        std::pair<long, bool> bounds(-3, false);
+        auto s = util::make_span(bounds);
+        auto first = s.front();
+        auto last = s.back();
+
+        EXPECT_EQ(-3, first);
+        EXPECT_EQ(-1, last);
+
+        EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value));
+        EXPECT_TRUE((std::is_same<long, decltype(first)>::value));
+    }
+}
+
diff --git a/tests/unit/test_transform.cpp b/tests/unit/test_transform.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9212293fbf7a59674eb81f3ccac1638b1df04077
--- /dev/null
+++ b/tests/unit/test_transform.cpp
@@ -0,0 +1,56 @@
+#include "gtest.h"
+
+#include <cctype>
+#include <forward_list>
+#include <vector>
+
+#include <util/range.hpp>
+#include <util/transform.hpp>
+
+using namespace nest::mc;
+
+TEST(transform, transform_view) {
+    std::forward_list<int> fl = {1, 4, 6, 8, 10 };
+    std::vector<double> result;
+
+    auto r = util::transform_view(fl, [](int i) { return i*i+0.5; });
+
+    EXPECT_EQ(5u, util::size(r));
+    EXPECT_EQ(16.5, *(std::next(std::begin(r), 1)));
+
+    std::copy(r.begin(), r.end(), std::back_inserter(result));
+    std::vector<double> expected = { 1.5, 16.5, 36.5, 64.5, 100.5 };
+
+    EXPECT_EQ(expected, result);
+}
+
+struct null_terminated_t {
+    bool operator==(const char *p) const { return !*p; }
+    bool operator!=(const char *p) const { return !!*p; }
+
+    friend bool operator==(const char *p, null_terminated_t x) {
+        return x==p;
+    }
+
+    friend bool operator!=(const char *p, null_terminated_t x) {
+        return x!=p;
+    }
+
+    constexpr null_terminated_t() {}
+};
+
+constexpr null_terminated_t null_terminated;
+
+char upper(char c) { return std::toupper(c); }
+
+TEST(transform, transform_view_sentinel) {
+    const char *hello = "hello";
+    auto r = util::transform_view(util::make_range(hello, null_terminated), upper);
+
+    std::string out;
+    for (auto i = r.begin(); i!=r.end(); ++i) {
+        out += *i;
+    }
+    EXPECT_EQ("HELLO", out);
+}
+