etaler / Etaler

A flexable HTM (Hierarchical Temporal Memory) framework with full GPU support.
BSD 3-Clause "New" or "Revised" License
89 stars 14 forks source link

Iteration over tensor #119

Closed marty1885 closed 4 years ago

marty1885 commented 4 years ago

To match numpy's behavior, we should make the Tensor object iteratable. And iterates over the first dim by default.

Ex:

a = ones({4, 4});
for(auto s : a)
    a += 1; 

etc...

marty1885 commented 4 years ago

This turns out to be quite of a problem. Since views are generated on the fly; the C++ iterator architecture is fitting badly.

//Iterator
struct ETALER_EXPORT iterator
{
    iterator(Tensor& t) : t_(&t) {}
    Tensor operator*() { return t_->view({curr_}); } // Can't return ref here
    Tensor* operator->() {/*real? How am I going to return a pointer from a local variable*/}

    bool operator==(iterator rhs) { return curr_ == rhs.curr_ && t_ == rhs.t_; }
    bool operator!=(iterator rhs) { !(*this == rhs); }
    iterator& operator++() {curr_ += 1; return *this;}
        iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
    Tensor* t_; // Using a pointer because Tensor is a incomplete type within the type itself
    intmax_t curr_ = 0;
};
marty1885 commented 4 years ago

This is the best I can do

template <typename T>
struct ETALER_EXPORT TensorIterator
{
    // Iterator properties
    using iterator_category = std::bidirectional_iterator_tag;
    using value_type = T;
    using raw_value_type = std::remove_const_t<value_type>; // extra
    using difference_type = intmax_t;
    using pointer = std::unique_ptr<raw_value_type>;
    using reference = T&;

    using ThisIterator = TensorIterator<T>;
    TensorIterator() = default;
    TensorIterator(reference t, intmax_t offset = 0) : t_(&t), offset_(offset)
    {static_assert(std::is_same_v<raw_value_type, Tensor>); }
    value_type operator*() { return t_->view({offset_}); }
    // Unfortunatelly returning a pointer is not doable
    pointer operator->() { return std::make_unique<raw_value_type>(*(*this)); }
    bool operator==(ThisIterator rhs) const { return offset_ == rhs.offset_ && t_ == rhs.t_; }
    bool operator!=(ThisIterator rhs) const { return !(*this == rhs); }
    ThisIterator& operator++() {offset_ += 1; return *this;}
    ThisIterator operator++(int) {ThisIterator retval = *this; ++(*this); return retval;}
    ThisIterator& operator--() {offset_ -= 1; return *this;}
    ThisIterator operator--(int) {ThisIterator retval = *this; --(*this); return retval;}
    value_type* t_ = nullptr; // Using a pointer because Tensor is a incomplete type here
    intmax_t offset_ = 0;
};

It works 90% of the time and have few caveats

marty1885 commented 4 years ago

121 implements a bidirectional iterator. It should be enough for most use cases.

Feel free to reopen the issue if we need a random access iterator