// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include <unordered_map>
#include <mutex>
#include <gsl/gsl>

#include <typeindex>

#include "arcana/containers/ticketed_collection.h"
#include "arcana/string.h"

#if NDEBUG
#define FIRE_OBJECT_TRACE(...)
#else
#define OBJECT_TRACE_ENABLED
#define FIRE_OBJECT_TRACE(channelName, instance, evt) \
    ::mira::object_trace::fire(channelName, instance, evt)
#endif

namespace mira
{
    /*
        Simple static analysis of data for exposing
    */
    class object_trace final
    {
        using channel_callback = std::function<void(std::intptr_t, const void*)>;
        using global_callback = std::function<void(const char*, std::intptr_t, const void*)>;

        using channel_ticketed_collection = ticketed_collection<channel_callback, std::recursive_mutex>;
        using global_ticketed_collection = ticketed_collection<global_callback, std::recursive_mutex>;
    public:
        using channel_ticket = channel_ticketed_collection::ticket;
        using global_ticket = global_ticketed_collection::ticket;

        object_trace() = delete;

        /*
        Adds a listener to type T on the specified channel.

        The channel is useful to split data into logical streams.
        */
        template<typename T>
        static auto listen(const char* channel, std::function<void(std::intptr_t, T&)> callback)
        {
            return add_listener(channel, std::type_index{ typeid(std::decay_t<T>) }, [callback = std::move(callback)](std::intptr_t instance, const void* data)
            {
                callback(instance, const_cast<T&>(*reinterpret_cast<const T*>(data)));
            });
        }

        /*
            Adds a listener to type T to all channels.
        */
        template<typename T>
        static auto listen(std::function<void(const char*, std::intptr_t, T&)> callback)
        {
            return add_listener(std::type_index{ typeid(std::decay_t<T>) },
                [callback = std::move(callback)](const char* channel, std::intptr_t instance, const void* data)
                {
                    callback(channel, instance, const_cast<T&>(*reinterpret_cast<const T*>(data)));
                });
        }

        /*
            Fires an event in the specified channel.
        */
        template<typename T>
        static void fire(const char* channel, const void* instance, const T& evt)
        {
            fire_event(channel, (std::intptr_t)instance, std::type_index{ typeid(std::decay_t<T>) }, &evt);
        }
    private:
        using channels = std::map<std::string, channel_ticketed_collection, string_compare>;

        struct listeners
        {
            channels ChannelListeners;
            global_ticketed_collection GlobalListeners;
        };

        static channel_ticket add_listener(const char* channel, const std::type_index& type, channel_callback callback);
        static global_ticket add_listener(const std::type_index& type, global_callback callback);

        static void fire_event(const char* channel, std::intptr_t instance, const std::type_index& type, const void* data);

        static std::recursive_mutex m_mutex;
        static std::unordered_map<std::type_index, listeners> m_typemap;
    };
}


#pragma once

#include <gsl.h>

#include <mitkCommon.h>
#include <mitkBaseData.h>

#include <vtkSmartPointer.h>
#include <vtkDataArray.h>

#include "SolverSetupServiceExports.h"

namespace crimson
{

/*! \brief   A class used for storing the simulation results and materials. */
class SolverSetupService_EXPORT SolutionData : public mitk::BaseData
{
public:
    mitkClassMacro(SolutionData, mitk::BaseData);
    mitkNewMacro1Param(Self, vtkSmartPointer<vtkDataArray>);
    //     itkCloneMacro(Self);
    //     mitkCloneMacro(Self);

    /*!
     * \brief   Gets the data in form of a vtkDataArray.
     */
    vtkDataArray* getArrayData() const;

protected:
    SolutionData(vtkSmartPointer<vtkDataArray> data);
    virtual ~SolutionData() {}

    SolutionData(const Self& other) = delete;

private:
    vtkSmartPointer<vtkDataArray> _data;

    void SetRequestedRegion(const itk::DataObject*) override {}
    void SetRequestedRegionToLargestPossibleRegion() override {}
    bool RequestedRegionIsOutsideOfTheBufferedRegion() override { return true; }
    bool VerifyRequestedRegion() override { return true; }
};
}


#pragma once

#include <bgfx/bgfx.h>
#include <napi/napi.h>
#include <gsl/gsl>
#include <map>
#include <optional>

namespace Babylon
{
    class VertexBuffer final
    {
    public:
        VertexBuffer(gsl::span<uint8_t> bytes, bool dynamic);
        ~VertexBuffer();

        void Dispose();

        void Update(Napi::Env env, gsl::span<uint8_t> bytes);
        bool CreateHandle(const bgfx::VertexLayout& layout);
        void PromoteToFloats(bgfx::AttribType::Enum attribType, uint32_t numElements, uint32_t byteOffset, uint32_t byteStride);
        void Set(bgfx::Encoder* encoder, uint8_t stream, uint32_t startVertex, uint32_t numVertices, bgfx::VertexLayoutHandle layoutHandle);

        struct InstanceVertexBufferRecord
        {
            VertexBuffer* Buffer{};
            uint32_t Offset{};
            uint32_t Stride{};
            uint32_t ElementSize{};
        };
        static void BuildInstanceDataBuffer(bgfx::InstanceDataBuffer& instanceDataBuffer, const std::map<bgfx::Attrib::Enum, InstanceVertexBufferRecord>& vertexBufferInstance);
    private:
        std::optional<std::vector<uint8_t>> m_bytes{};
        bool m_dynamic{};

        union
        {
            bgfx::VertexBufferHandle m_handle{bgfx::kInvalidHandle};
            bgfx::DynamicVertexBufferHandle m_dynamicHandle;
        };

        bool m_disposed{};
    };
}


/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2017 Couchbase, Inc
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

#pragma once

#include <gsl_p/span.h>

namespace phosphor {

/**
 * Pure virtual base class for receiving stats from phosphor internals
 *
 * Methods on this class will be called with the key and value of each stat.
 * Callback implementations MUST NOT re-enter the TraceLog or TraceBuffer
 * upon which it used as locks may be held when the callback is invoked.
 *
 * Example usage:
 *
 *     class MyStatsCallback : public phosphor::StatsCallback {
 *          // Implement callback methods
 *     } callback;
 *
 *     phosphor::TraceLog::getInstance().getStats(callback);
 *
 *     // Stash data as required for application
 *     auto data = callback.getData();
 *
 * Implementations should note that phosphor makes no guarantees about
 * atomicity of the stats with respect to each other.
 */
class StatsCallback {
public:
    /**
     * Utility template method for passing a string literal as the first
     * argument instead of a string span
     * @param s String to convert to a span
     * @param value Value to forward
     */
    template <size_t N, typename T>
    void operator()(const char (&s)[N], T&& value) {
        this->operator()(gsl_p::make_span(s), std::forward<T>(value));
    }

    virtual void operator()(gsl_p::cstring_span key,
                            gsl_p::cstring_span value) = 0;
    virtual void operator()(gsl_p::cstring_span key, bool value) = 0;
    virtual void operator()(gsl_p::cstring_span key, size_t value) = 0;
    virtual void operator()(gsl_p::cstring_span key, ssize_t value) = 0;
    virtual void operator()(gsl_p::cstring_span key, double value) = 0;
};

}


#pragma once

#include "options.h"
#include "predict.h"
#include "progress.h"
#include <gsl/gsl-lite.hpp>

namespace angonoka::cli {
namespace detail {
    /**
        Check if an event is the final one.

        @param evt Event

        @return True if this is the final event.
    */
    bool is_final_event(ProgressEvent& evt) noexcept;

    /**
        Helper constant that checks if a given class is
        a specialization of std::future.
    */
    template <typename T> inline constexpr bool is_future = false;
    template <typename T>
    inline constexpr bool is_future<std::future<T>> = true;
} // namespace detail

/**
    Prediction events handler.

    Prints various progress messages and results as
    the prediction algorithm runs.

    @var progress Chosen progress bar implementation
    @var options CLI options
*/
struct EventHandler {
    gsl::not_null<Progress*> progress;
    gsl::not_null<const Options*> options;

    /**
        Handler events without attributes.

        @param e Event
    */
    void operator()(const SimpleProgressEvent& e) const;

    /**
        Handle schedule optimization events.

        @param e Event
    */
    void operator()(const ScheduleOptimizationEvent& e) const;

    /**
        Handle schedule optimization completion.

        @param e Event
    */
    void operator()(const ScheduleOptimizationComplete& e) const;
};

/**
    Awaitable result of the prediction.
*/
template <typename T>
concept Prediction = detail::is_future<T>;

/**
    Consumes prediction events from the queue.

    @param queue        Prediction events queue
    @param prediction   Prediction result
    @param handler      Events handler
*/
void consume_events(
    Queue<ProgressEvent>& queue,
    Prediction auto& prediction,
    EventHandler handler)
{
    Expects(prediction.valid());

    using namespace std::literals::chrono_literals;
    using boost::variant2::visit;
    constexpr auto event_timeout = 100ms;
    ProgressEvent evt;
    while (!detail::is_final_event(evt)) {
        if (!queue.try_dequeue(evt)) {
            prediction.wait_for(event_timeout);
            continue;
        }
        visit(handler, evt);
    }

    Ensures(prediction.valid());
}
} // namespace angonoka::cli


/*
 * Copyright (c) 2016-2021 lymastee, All rights reserved.
 * Contact: lymastee@hotmail.com
 *
 * This file is part of the gslib project.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#pragma once

#ifndef avl_6c9ab2d0_f8bc_4fb1_bc21_7fc0c6eb4bec_h
#define avl_6c9ab2d0_f8bc_4fb1_bc21_7fc0c6eb4bec_h

#include <assert.h>
#include <gslib/std.h>

__gslib_begin__

struct _avltree_trait_copy {};
struct _avltree_trait_detach {};

template<class _ty>
struct _avltreenode_cpy_wrapper
{
    typedef _ty value;
    typedef _avltreenode_cpy_wrapper<_ty> myref;
    typedef _avltree_trait_copy tsf_behavior;

    value               _value;
    myref*              _left;
    myref*              _right;
    myref*              _parent;
    int                 _balance;

    myref()
    {
        _left = _right = _parent = nullptr;
        _balance = 0;
    }
    value* get_ptr() { return &_value; }
    const value* const_ptr() const { return &_value; }
    value& get_ref() { return _value; }
    const value& const_ref() const { return _value; }
    void born() {}
    void kill() {}
    template<class _ctor>
    void born() {}
    template<class _ctor>
    void kill() {}
    void copy(const myref* a) { get_ref() = a->const_ref(); }
    void attach(myref* a) { assert(0); }
    void swap_data(myref* a) { std::swap(_value, a->_value); }
};

template<class _ty>
struct _avltreenode_wrapper
{
    typedef _ty value;
    typedef _avltreenode_wrapper<_ty> myref;
    typedef _avltree_trait_detach tsf_behavior;

    value*              _value;
    myref*              _left;
    myref*              _right;
    myref*              _parent;
    int                 _balance;

    myref()
    {
        _left = _right = _parent = nullptr;
        _value = nullptr;
        _balance = 0;
    }
    value* get_ptr() { return _value; }
    const value* const_ptr() const { return _value; }
    value& get_ref() { return *_value; }
    const value& const_ref() const { return *_value; }
    void copy(const myref* a) { get_ref() = a->const_ref(); }
    void born() { !! }
    template<class _ctor>
    void born() { _value = new _ctor; }
    void kill() { if(_value) { delete _value; _value = nullptr; } }
    template<class _ctor>
    void kill() { if(_value) { delete _value; _value = nullptr; } }
    void attach(myref* a)
    {
        assert(a && a->_value);
        kill();
        _value = a->_value;
        a->_value = nullptr;
    }
    void swap_data(myref* a) { gs_swap(_value, a->_value); }
};

template<class _wrapper>
struct _avltree_allocator
{
    typedef _wrapper wrapper;
    static wrapper* born() { return new wrapper; }
    static void kill(wrapper* w) { delete w; }
};

template<class _val>
struct _avltreenode_val
{
    typedef _val value;
    typedef const _val const_value;
    typedef _avltreenode_val<_val> myref;

    union
    {
        value*          _vptr;
        const_value*    _cvptr;
    };

    myref() { _vptr = nullptr; }
    value* get_wrapper() const { return _vptr; }
    operator bool() const { return _vptr != nullptr; }
    bool is_left() const { return (_cvptr && _cvptr->_parent) ? _cvptr->_parent->_left == _cvptr : false; }
    bool is_right() const { return (_cvptr && _cvptr->_parent) ? _cvptr->_parent->_right == _cvptr : false; }
    bool is_root() const { return _cvptr ? (!_cvptr->_parent) : false; }
    bool is_leaf() const { return _cvptr ? (!_cvptr->_left && !_cvptr->_right) : false; }
    int up_depth() const
    {
        int depth = 0;
        for(value* p = _vptr; p; p = p->_parent, depth ++);
        return depth;
    }
    int down_depth() const { return _down_depth(_vptr, 0); }
    bool operator==(const value* v) const { return _vptr == v; }
    bool operator!=(const value* v) const { return _vptr != v; }
    int get_balance() const { return _vptr->_balance; }
    void set_balance(int b) { _vptr->_balance = b; }
    void swap_data(myref& a) { _vptr->swap_data(a._vptr); }

public:
    static void connect_left_child(value* p, value* l)
    {
        assert(p);
        p->_left = l;
        if(l)
            l->_parent = p;
    }
    static void connect_right_child(value* p, value* r)
    {
        assert(p);
        p->_right = r;
        if(r)
            r->_parent = p;
    }
    static bool disconnect_parent_child(value* p, value* c)
    {
        assert(p && c && (c->_parent == p));
        if(p->_left == c) {
            p->_left = c->_parent = nullptr;
            return true;
        }
        assert(p->_right == c);
        p->_right = c->_parent = nullptr;
        return false;
    }

private:
    static int _down_depth(value* v, int ctr)
    {
        if(v == nullptr)
            return ctr;
        ctr ++;
        return gs_max(_down_depth(v->_left, ctr),
            _down_depth(v->_right, ctr)
            );
    }

protected:
    value* vleft() const { return _vptr ? _vptr->_left : nullptr; }
    value* vright() const { return _vptr ? _vptr->_right : nullptr; }
    value* vparent() const { return _vptr ? _vptr->_parent : nullptr; }
    value* vsibling() const
    {
        if(!_vptr || !_vptr->_parent)
            return nullptr;
        if(is_left())
            return _vptr->_right;
        else if(is_right())
            return _vptr->_left;
        assert(!"unexpected.");
        return nullptr;
    }
    value* vroot() const
    {
        if(!_vptr)
            return nullptr;
        value* p = _vptr;
        for( ; p->_parent; p = p->_parent);
        return p;
    }

protected:
    template<class _lambda, class _value>
    static void preorder_traversal(_lambda lam, _value* v)
    {
        assert(v);
        lam(v);
        if(v->_left)
            preorder_traversal(lam, v->_left);
        if(v->_right)
            preorder_traversal(lam, v->_right);
    }
    template<class _lambda, class _value>
    static void inorder_traversal(_lambda lam, _value* v)
    {
        assert(v);
        if(v->_left)
            inorder_traversal(lam, v->_left);
        lam(v);
        if(v->_right)
            inorder_traversal(lam, v->_right);
    }
    template<class _lambda, class _value>
    static void postorder_traversal(_lambda lam, _value* v)
    {
        assert(v);
        if(v->_left)
            postorder_traversal(lam, v->_left);
        if(v->_right)
            postorder_traversal(lam, v->-right);
        lam(v);
    }

public:
    template<class _lambda>
    void inorder_traversal(_lambda lam) { if(_vptr) inorder_traversal(lam, _vptr); }
    template<class _lambda>
    void inorder_traversal(_lambda lam) const { if(_cvptr) inorder_traversal(lam, _cvptr); }
    template<class _lambda>
    void preorder_traversal(_lambda lam) { if(_vptr) preorder_traversal(lam, _vptr); }
    template<class _lambda>
    void preorder_traversal(_lambda lam) const { if(_cvptr) preorder_traversal(lam, _cvptr); }
    template<class _lambda>
    void postorder_traversal(_lambda lam) { if(_vptr) postorder_traversal(lam, _vptr); }
    template<class _lambda>
    void postorder_traversal(_lambda lam) const { if(_cvptr) postorder_traversal(lam, _cvptr); }
};

template<class _ty,
    class _wrapper = _avltreenode_cpy_wrapper<_ty> >
class _avltree_const_iterator:
    public _avltreenode_val<_wrapper>
{
public:
    typedef _ty value;
    typedef _wrapper wrapper;
    typedef _avltree_const_iterator<_ty, _wrapper> iterator;

public:
    iterator(const wrapper* w = nullptr) { _cvptr = w; }
    bool is_valid() const { return _cvptr != nullptr; }
    const value* get_ptr() const { return _cvptr->const_ptr(); }
    const value* operator->() const { return _cvptr->const_ptr(); }
    const value& operator*() const { return _cvptr->const_ref(); }
    iterator left() const { return iterator(vleft()); }
    iterator right() const { return iterator(vright()); }
    iterator parent() const { return iterator(vparent()); }
    iterator sibling() const { return iterator(vsibling()); }
    iterator root() const { return iterator(vroot()); }
    bool operator==(const iterator& that) const { return _cvptr == that._cvptr; }
    bool operator!=(const iterator& that) const { return _cvptr != that._cvptr; }
};

template<class _ty,
    class _wrapper = _avltreenode_cpy_wrapper<_ty> >
class _avltree_iterator:
    public _avltree_const_iterator<_ty, _wrapper>
{
public:
    typedef _ty value;
    typedef _wrapper wrapper;
    typedef _avltree_const_iterator<_ty, _wrapper> const_iterator;
    typedef _avltree_const_iterator<_ty, _wrapper> superref;
    typedef _avltree_iterator<_ty, _wrapper> iterator;

public:
    iterator(wrapper* w): superref(w) {}
    value* get_ptr() const { return _vptr->get_ptr(); }
    value* operator->() const { return _vptr->get_ptr(); }
    value& operator*() const { return _vptr->get_ref(); }
    bool operator==(const iterator& that) const { return _vptr == that._vptr; }
    bool operator!=(const iterator& that) const { return _vptr != that._vptr; }
    bool operator==(const const_iterator& that) const { return _vptr == that._vptr; }
    bool operator!=(const const_iterator& that) const { return _vptr != that._vptr; }
    operator const_iterator() { return const_iterator(_cvptr); }
    void to_root() { _vptr = vroot(); }
    void to_left() { _vptr = vleft(); }
    void to_right() { _vptr = vright(); }
    void to_sibling() { _vptr = vsibling(); }
    void to_parent() { _vptr = vparent(); }
    iterator left() const { return iterator(vleft()); }
    iterator right() const { return iterator(vright()); }
    iterator parent() const { return iterator(vparent()); }
    iterator sibling() const { return iterator(vsibling()); }
    iterator root() const { return iterator(vroot()); }
};

template<class _ty,
    class _wrapper = _avltreenode_cpy_wrapper<_ty>,
    class _alloc = _avltree_allocator<_wrapper> >
class avltree:
    public _avltreenode_val<_wrapper>
{
public:
    typedef _ty value;
    typedef _wrapper wrapper;
    typedef _alloc alloc;
    typedef avltree<value, wrapper, alloc> myref;
    typedef _avltree_const_iterator<_ty, _wrapper> const_iterator;
    typedef _avltree_iterator<_ty, _wrapper> iterator;

public:
    avltree() { _vptr = nullptr; }
    ~avltree() { clear(); }
    void clear() { destroy(get_root()); }
    void destroy(iterator i)
    {
        if(!i.is_valid())
            return;
        if(iterator p = i.parent())
            disconnect_parent_child(p.get_wrapper(), i.get_wrapper());
        else {
            assert(is_root(i));
            _vptr = nullptr;
        }
        _destroy(i);
    }
    void adopt(wrapper* w)
    {
        assert(!_vptr && "use attach method.");
        _vptr = w;
    }
    iterator get_root() const { return iterator(_vptr); }
    const_iterator const_root() const { return const_iterator(_cvptr); }
    bool is_root(iterator i) const { return i.is_valid() ? (_cvptr == i.get_wrapper()) : false; }
    bool is_valid() const { return _cvptr != nullptr; }
    bool is_mine(iterator i) const
    {
        if(!i.is_valid())
            return false;
        i.to_root();
        return i.get_wrapper() == _vptr;
    }
    int depth() const { return _cvptr->down_depth(); }
    void swap(myref& that) { gs_swap(_vptr, that._vptr); }
    iterator find(const value& v) const { return find(get_root(), v); }
    iterator find(iterator i, const value& v) const
    {
        if(!i) {
            if(!is_valid())
                return i;
            i = get_root();
        }
        assert(i);
        if(v == *i)
            return i;
        iterator n = (v < *i) ? i.left() : i.right();
        return n ? find(n, v) : i;
    }
    template<class _ctor = value>
    iterator insert(const value& v)
    {
        iterator i = get_root();
        return !i ? _init<_ctor>(v) : _insert<_ctor>(i, v);
    }
    void erase(iterator i)
    {
        assert(i && is_mine(i));
        if(i.is_leaf()) {
            iterator p = i.parent();
            if(p) {
                disconnect_parent_child(p.get_wrapper(), i.get_wrapper());
                _destroy(i);
                _balance_erase(p);
                return;
            }
            else {
                assert(is_root(i));
                _destroy(i);
                _vptr = nullptr;
                return;
            }
        }
        if(i.left()) {
            if(i.right()) {     /* left & right */
                iterator l = i.left();
                iterator li = l;
                for(; li.right(); li.to_right());
                iterator lil = li.left();
                i.swap_data(li);
                if(lil) {
                    li.swap_data(lil);
                    destroy(lil);
                    _balance_erase(li);
                    return;
                }
                else {
                    iterator q = li.parent();
                    assert(q);
                    destroy(li);
                    _balance_erase(q);
                    return;
                }
            }
            else {      /* only left */
                if(i.parent()) {
                    myref t;
                    iterator j = attach(detach(t, i.left()), i);
                    j.set_balance(i.get_balance());
                    _balance_erase(j);
                    return;
                }
                else {
                    myref t;
                    swap(detach(t, i.left()));
                    return;
                }
            }
        }
        else {
            assert(i.right());      /* only right */
            if(i.parent()) {
                myref t;
                iterator j = attach(detach(t, i.right()), i);
                j.set_balance(i.get_balance());
                _balance_erase(j);
                return;
            }
            else {
                myref t;
                swap(detach(t, i.right()));
                return;
            }
        }
    }
    void erase(const value& v)
    {
        if(iterator i = find(v))
            erase(i);
    }

    /* The detach and attach methods, provide subtree operations */
    myref& detach(myref& subtree, iterator i)
    {
        assert(i && is_mine(i));
        if(subtree.is_valid())
            subtree.clear();
        detach<myref>(subtree, i);
        return subtree;
    }
    template<class _cont>
    void detach(_cont& cont)
    {
        cont.adopt(_vptr);
        _vptr = nullptr;
    }
    template<class _cont>
    void detach(_cont& cont, iterator i)
    {
        assert(i && is_mine(i));
        if(i == get_root())
            return detach(cont);
        iterator p = i.parent();
        assert(p);
        disconnect_parent_child(p.get_wrapper(), i.get_wrapper());
        cont.adopt(i.get_wrapper());
    }
    iterator attach(myref& subtree, iterator i)
    {
        assert(i && is_mine(i) && i.is_leaf());
        if(i.is_root()) {
            swap(subtree);
            return get_root();
        }
        iterator p = i.parent();
        assert(p);
        bool leftp = disconnect_parent_child(p.get_wrapper(), i.get_wrapper());
        gs_swap(subtree._vptr, i._vptr);
        leftp ? connect_left_child(p.get_wrapper(), i.get_wrapper()) :
            connect_right_child(p.get_wrapper(), i.get_wrapper());
        subtree.clear();
        return i;
    }

public:
    template<class _lambda>
    void preorder_for_each(_lambda lam) { preorder_traversal([](wrapper* w) { lam(w->get_ptr()); }); }
    template<class _lambda>
    void preorder_const_for_each(_lambda lam) const { preorder_traversal([](const wrapper* w) { lam(w->const_ptr()); }); }
    template<class _lambda>
    void inorder_for_each(_lambda lam) { inorder_traversal([](wrapper* w) { lam(w->get_ptr()); }); }
    template<class _lambda>
    void inorder_const_for_each(_lambda lam) const { inorder_traversal([](const wrapper* w) { lam(w->const_ptr()); }); }
    template<class _lambda>
    void postorder_for_each(_lambda lam) { postorder_traversal([](wrapper* w) { lam(w->get_ptr()); }); }
    template<class _lambda>
    void postorder_const_for_each(_lambda lam) const { postorder_traversal([](const wrapper* w) { lam(w->const_ptr()); }); }

protected:
    void _destroy(iterator i)
    {
        if(!i.is_valid())
            return;
        _destroy(i.left());
        _destroy(i.right());
        wrapper* w = i.get_wrapper();
        w->kill();
        alloc::kill(w);
    }
    template<class _ctor>
    iterator _insert(iterator i, const value& v)
    {
        assert(i);
        if(v == *i)
            return iterator(nullptr);     /* failed */
        if(v < *i) {
            if(i.left())
                return _insert<_ctor>(i.left(), v);
            iterator j = _add_left<_ctor>(i, v);
            _balance_insert(j);
            return j;
        }
        else {
            if(i.right())
                return _insert<_ctor>(i.right(), v);
            iterator j = _add_right<_ctor>(i, v);
            _balance_insert(j);
            return j;
        }
    }
    void _balance_insert(iterator i)
    {
        assert(i);
        iterator p = i.parent();
        if(!p)
            return;
        if(i.is_left()) {
            switch(p.get_balance())
            {
            case -1:
                (i.get_balance() == 1) ?
                    _left_right_rotate(p) :
                    _right_rotate(p);
                break;
            case 0:
                p.set_balance(-1);
                return _balance_insert(p);
            case 1:
                p.set_balance(0);
                break;
            default:
                assert(!"unexpected.");
                break;
            }
        }
        else {
            switch(p.get_balance())
            {
            case -1:
                p.set_balance(0);
                break;
            case 0:
                p.set_balance(1);
                return _balance_insert(p);
            case 1:
                (i.get_balance() == -1) ?
                    _right_left_rotate(p) :
                    _left_rotate(p);
                break;
            default:
                assert(!"unexpected.");
                break;
            }
        }
    }
    void _balance_erase(iterator i)
    {
        assert(i);
        int b = i.get_balance();
        if(!b) {
            i.set_balance(i.left() ? -1 : 1);
            return;
        }
        if(!i.left()) {
            if(b == -1)
                i.set_balance(0);
            else if(b == 1) {
                iterator r = i.right();
                if(!r)
                    i.set_balance(0);
                else {
                    (r.get_balance() == -1) ?
                        _right_left_rotate(i) :
                        _left_rotate(i);
                    i.to_parent();
                    if(i.get_balance() == -1)
                        return;
                }
            }
            else {
                assert(!"unexpected.");
            }
        }
        else if(!i.right()) {
            if(b == 1)
                i.set_balance(0);
            else if(b == -1) {
                iterator l = i.left();
                if(!l)
                    i.set_balance(0);
                else {
                    (l.get_balance() == 1) ?
                        _left_right_rotate(i) :
                        _right_rotate(i);
                    i.to_parent();
                    if(i.get_balance() == 1)
                        return;
                }
            }
            else {
                assert(!"unexpected.");
            }
        }
        _balance_erase_(i);
    }
    void _balance_erase_(iterator i)
    {
        assert(i);
        iterator p = i.parent();
        if(!p)
            return;
        if(i.is_left()) {
            switch(p.get_balance())
            {
            case -1:
                p.set_balance(0);
                return _balance_erase_(p);
            case 0:
                p.set_balance(1);
                break;
            case 1:
                (p.right().get_balance() == -1) ?
                    _right_left_rotate(p) :
                    _left_rotate(p);
                if(p.parent().get_balance() != -1)
                    return _balance_erase_(p.parent());
                break;
            default:
                assert(!"unexpected.");
                break;
            }
        }
        else {
            switch(p.get_balance())
            {
            case -1:
                (p.left().get_balance() == 1) ?
                    _left_right_rotate(p) :
                    _right_rotate(p);
                if(p.parent().get_balance() != 1)
                    return _balance_erase_(p.parent());
                break;
            case 0:
                p.set_balance(-1);
                break;
            case 1:
                p.set_balance(0);
                return _balance_erase_(p);
            default:
                assert(!"unexpected.");
                break;
            }
        }
    }
    iterator _left_rotate(iterator i)
    {
        assert(i && i.right());
        iterator p = i.parent();
        iterator r = i.right();
        iterator rl = r.left();
        if(p) {
            i.is_left() ? connect_left_child(p.get_wrapper(), r.get_wrapper()) :
                connect_right_child(p.get_wrapper(), r.get_wrapper());
        }
        else {
            _vptr = r.get_wrapper();
            _vptr->_parent = nullptr;
        }
        connect_left_child(r.get_wrapper(), i.get_wrapper());
        connect_right_child(i.get_wrapper(), rl.get_wrapper());
        if(r.get_balance() == 0) {
            i.set_balance(1);
            r.set_balance(-1);
        }
        else {
            i.set_balance(0);
            r.set_balance(0);
        }
        return r;
    }
    iterator _right_rotate(iterator i)
    {
        assert(i && i.left());
        iterator p = i.parent();
        iterator l = i.left();
        iterator lr = l.right();
        if(p) {
            i.is_left() ? connect_left_child(p.get_wrapper(), l.get_wrapper()) :
                connect_right_child(p.get_wrapper(), l.get_wrapper());
        }
        else {
            _vptr = l.get_wrapper();
            _vptr->_parent = nullptr;
        }
        connect_right_child(l.get_wrapper(), i.get_wrapper());
        connect_left_child(i.get_wrapper(), lr.get_wrapper());
        if(l.get_balance() == 0) {
            i.set_balance(-1);
            l.set_balance(1);
        }
        else {
            i.set_balance(0);
            l.set_balance(0);
        }
        return l;
    }
    iterator _left_right_rotate(iterator i)
    {
        assert(i && i.left());
        iterator l = i.left();
        iterator lr = l.right();
        assert(lr);
        iterator lrl = lr.left();
        iterator lrr = lr.right();
        iterator p = i.parent();
        if(p) {
            i.is_left() ? connect_left_child(p.get_wrapper(), lr.get_wrapper()) :
                connect_right_child(p.get_wrapper(), lr.get_wrapper());
        }
        else {
            _vptr = lr.get_wrapper();
            _vptr->_parent = nullptr;
        }
        connect_left_child(lr.get_wrapper(), l.get_wrapper());
        connect_right_child(lr.get_wrapper(), i.get_wrapper());
        connect_right_child(l.get_wrapper(), lrl.get_wrapper());
        connect_left_child(i.get_wrapper(), lrr.get_wrapper());
        switch(lr.get_balance())
        {
        case -1:
            i.set_balance(1);
            l.set_balance(0);
            break;
        case 0:
            i.set_balance(0);
            l.set_balance(0);
            break;
        case 1:
            i.set_balance(0);
            l.set_balance(-1);
            break;
        default:
            assert(!"unexpected.");
            break;
        }
        lr.set_balance(0);
        return lr;
    }
    iterator _right_left_rotate(iterator i)
    {
        assert(i && i.right());
        iterator r = i.right();
        iterator rl = r.left();
        assert(rl);
        iterator rll = rl.left();
        iterator rlr = rl.right();
        iterator p = i.parent();
        if(p) {
            i.is_left() ? connect_left_child(p.get_wrapper(), rl.get_wrapper()) :
                connect_right_child(p.get_wrapper(), rl.get_wrapper());
        }
        else {
            _vptr = rl.get_wrapper();
            _vptr->_parent = nullptr;
        }
        connect_right_child(rl.get_wrapper(), r.get_wrapper());
        connect_left_child(rl.get_wrapper(), i.get_wrapper());
        connect_right_child(i.get_wrapper(), rll.get_wrapper());
        connect_left_child(r.get_wrapper(), rlr.get_wrapper());
        switch(rl.get_balance())
        {
        case -1:
            i.set_balance(0);
            r.set_balance(1);
            break;
        case 0:
            i.set_balance(0);
            r.set_balance(0);
            break;
        case 1:
            i.set_balance(-1);
            r.set_balance(0);
            break;
        default:
            assert(!"unexpected.");
            break;
        }
        rl.set_balance(0);
        return rl;
    }

    template<class _ctor>
    iterator _init()
    {
        assert(!_vptr);
        _vptr = alloc::born();
        _vptr->born<_ctor>();
        return iterator(_vptr);
    }
    template<class _ctor>
    iterator _init(const value& v)
    {
        assert(!_vptr);
        _vptr = initval<_ctor, wrapper::tsf_behavior>::run(alloc::born(), v);
        assert(_vptr);
        return iterator(_vptr);
    }
    template<class _ctor>
    iterator _add_left(iterator i, const value& v)
    {
        assert(i && !i.left());
        wrapper* n = initval<_ctor, wrapper::tsf_behavior>::run(alloc::born(), v);
        connect_left_child(i.get_wrapper(), n);
        return iterator(n);
    }
    template<class _ctor>
    iterator _add_right(iterator i, const value& v)
    {
        assert(i && !i.right());
        wrapper* n = initval<_ctor, wrapper::tsf_behavior>::run(alloc::born(), v);
        connect_right_child(i.get_wrapper(), n);
        return iterator(n);
    }
    void _modified()
    {
#if defined (DEBUG) || defined (_DEBUG)
        debug_check(get_root());
#endif
    }

protected:
    friend struct initval;
    template<class _ctor, class _tsftrait>
    struct initval;
    template<class _ctor>
    struct initval<_ctor, _avltree_trait_copy>
    {
        static wrapper* run(wrapper* w, const value& v)
        {
            assert(w);
            w->get_ref() = v;
            return w;
        }
    };
    template<class _ctor>
    struct initval<_ctor, _avltree_trait_detach>
    {
        static wrapper* run(wrapper* w, const value& v)
        {
            assert(w);
            w->_value = &v;     /* or duplicate? */
            return w;
        }
    };

public:
    bool debug_check(iterator i)
    {
        if(!i)
            return true;
        check_root(i);
        check_linkage(i);
        check_order(i);
        check_balance(i);
        iterator l = i.left(), r = i.right();
        if(!(l && debug_check(l)))
            return false;
        if(!(r && debug_check(r)))
            return false;
        return true;
    }
    bool check_root(iterator i)
    {
        assert(i);
        if(is_root(i)) {
            assert(!i.parent() && "root has no parent.");
            return true;
        }
        assert(i.parent() && "non-root must have parent.");
        return false;
    }
    void check_linkage(iterator i)
    {
        assert(i);
        iterator l = i.left(), r = i.right();
        if(l) { assert(l.parent() == i && "left link wrong."); }
        if(r) { assert(r.parent() == i && "right link wrong."); }
    }
    void check_order(iterator i)
    {
        assert(i);
        iterator l = i.left(), r = i.right();
        if(l) {
            iterator lm = l;
            for(; lm.right(); lm = lm.right());
            assert(*lm < *i && "left order wrong.");
        }
        if(r) {
            iterator rm = r;
            for(; rm.left(); rm = rm.left());
            assert(*i < *rm && "right order wrong.");
        }
    }
    void check_balance(iterator i)
    {
        assert(i);
        iterator l = i.left(), r = i.right();
        int h1 = 0, h2 = 0;
        if(l) h1 = l.down_depth();
        if(r) h2 = r.down_depth();
        int b = i.get_balance();
        assert(b == h2 - h1 && "balance wrong.");
    }
};

__gslib_end__

#endif


////////////////////////////////////////////////////////////
//
// Copyright (c) 2018 Jan Filipowicz, Filip Turobos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////

#ifndef GENETIC_ALGORITHM_LIBRARY_EVALUATED_SPECIMEN_H
#define GENETIC_ALGORITHM_LIBRARY_EVALUATED_SPECIMEN_H

#include <optional>
#include <utility>
#include <tuple>
#include <type_traits>
#include <gsl/gsl_assert>

template<class Specimen, class Rating>
class evaluated_specimen {
public:
	using value_type = Specimen;
	using rating_type = Rating;
	template<class... Args, class = std::enable_if_t<!std::is_same_v<std::tuple<std::decay_t<Args>...>, std::tuple<evaluated_specimen>>>>
	constexpr evaluated_specimen(Args&&... args) noexcept(sizeof...(Args) == 0);
	constexpr value_type& value() & noexcept;
	constexpr const value_type& value() const& noexcept;
	constexpr bool has_rating() const noexcept;
	constexpr rating_type rating() const;
	template<class Function>
	void evaluate(Function&& evaluator);
private:
	value_type specimen;
	std::optional<rating_type> grade;
};

template<class Specimen, class Rating>
template<class... Args, class>
constexpr evaluated_specimen<Specimen, Rating>::evaluated_specimen(Args&&... args) noexcept(sizeof...(Args) == 0)
	: specimen(std::forward<Args>(args)...) {}

template<class Specimen, class Rating>
constexpr auto evaluated_specimen<Specimen, Rating>::value() & noexcept -> value_type& {
	return specimen;
}

template<class Specimen, class Rating>
constexpr auto evaluated_specimen<Specimen, Rating>::value() const& noexcept -> const value_type& {
	return specimen;
}

template<class Specimen, class Rating>
constexpr bool evaluated_specimen<Specimen, Rating>::has_rating() const noexcept {
	return grade.has_value();
}

template<class Specimen, class Rating>
constexpr auto evaluated_specimen<Specimen, Rating>::rating() const -> rating_type {
	return grade.value();
}

template<class Specimen, class Rating>
template<class Function>
inline void evaluated_specimen<Specimen, Rating>::evaluate(Function&& evaluator) {
	grade.emplace(evaluator(std::as_const(specimen)));
	Ensures(has_rating());
}

#endif


/* Copyright (c) 2011-2019, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#pragma once

#include <util.h>

#include <amg.h>
#include <basic_types.h>
#include <types.h>
#include <norm.h>
#include <logger.h>
#include <matrix_distribution.h>

#include <iostream>
#include <iomanip>
#include <blas.h>
#include <multiply.h>

#include <amg_level.h>
#include <amgx_c.h>
#include <profile.h>

#include <misc.h>
#include <string>
#include <cassert>
#include <csr_multiply.h>
#include <memory_info.h>

#include <thrust/sort.h>
#include <thrust/remove.h>
#include <thrust/unique.h>
#include <thrust/binary_search.h>
#include <thrust/iterator/constant_iterator.h>

#define COARSE_CLA_CONSO 0 // used enable / disable coarse level consolidation (used in cycles files)

namespace amgx
{

/**********************************************************
 * Glue ( Consolidation)
 *********************************************************/
#ifdef AMGX_WITH_MPI

//------------------------
//---------Matrix-------------
//------------------------
template<class TConfig>
void compute_glue_info(Matrix<TConfig> &A)
{
    // Fill distributed manager fields for consolidation
    // Example
    // destination_part = [0 0 0 0 4 4 4 4 8 8 8 8] (input from manager->computeDestinationPartitions)
    // num_parts_to_consolidate = 4 for partitions 0,4,8 - (0 otherwise)
    // parts_to_consolidate (rank 0)[0 1 2 3] (rank 4)[4 5 6 7] (rank 8)[8 9 10 11]
    //coarse_part_to_fine_part = [0 4 8] num_coarse_partitions = 3
    //fine_part_to_coarse_part = [0 0 0 0 1 1 1 1 2 2 2 2]
    //ConsolidationArrayOffsets constains the offset of the nnz of each partitions in row pointer fashion : 0, pat1.NNZ, pat1.NNZ+part2.NNZ ... NNZ
    typedef typename TConfig::template setMemSpace<AMGX_host>::Type TConfig_h;
    typedef typename TConfig_h::template setVecPrec<AMGX_vecInt>::Type ivec_value_type_h;
    typedef typename ivec_value_type_h::VecPrec VecInt_t;

    bool is_root_partition = false;
    int num_parts_to_consolidate = 0;
    int num_parts = A.manager->getComms()->get_num_partitions();
    int my_id = A.manager->global_id();
    Vector<ivec_value_type_h> parts_to_consolidate;
    Vector<ivec_value_type_h> dest_partitions = A.manager->getDestinationPartitions();

    // compute is_root_partition and num_parts_to_consolidate
    for (int i = 0; i < num_parts; i++)
    {
        if (dest_partitions[i] == my_id)
        {
            is_root_partition = true;
            num_parts_to_consolidate++;
        }
    }

    parts_to_consolidate.resize(num_parts_to_consolidate);
    // parts_to_consolidate
    int count = 0;

    for (int i = 0; i < num_parts; i++)
    {
        if (dest_partitions[i] == my_id)
        {
            parts_to_consolidate[count] = i;
            count++;
        }
    }

    A.manager->setIsRootPartition(is_root_partition);
    A.manager->setNumPartsToConsolidate(num_parts_to_consolidate);
    A.manager->setPartsToConsolidate(parts_to_consolidate);
    // We don't really use the following in the latest version of the glue path but they are useful information
    // coarse_to_fine_part, fine_to_coarse_part
    Vector<ivec_value_type_h> coarse_to_fine_part, fine_to_coarse_part(num_parts);
    coarse_to_fine_part = dest_partitions;
    thrust::sort(coarse_to_fine_part.begin(), coarse_to_fine_part.end());
    cudaCheckError();
    coarse_to_fine_part.erase(thrust::unique(coarse_to_fine_part.begin(), coarse_to_fine_part.end()), coarse_to_fine_part.end());
    cudaCheckError();
    thrust::lower_bound(coarse_to_fine_part.begin(), coarse_to_fine_part.end(), dest_partitions.begin(), dest_partitions.end(), fine_to_coarse_part.begin());
    cudaCheckError();
    A.manager->setCoarseToFine(coarse_to_fine_part);
    A.manager->setFineToCoarse(fine_to_coarse_part);
    Vector<ivec_value_type_h> consolidationArrayOffsets;
    consolidationArrayOffsets.resize(num_parts);
}

template<class TConfig>
MPI_Comm compute_glue_matrices_communicator(Matrix<TConfig> &A)
{
    // Create temporary communicators for each consilidated matrix
    int rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (A.manager->getDestinationPartitions().size() != 0)
    {
        int color = A.manager->getDestinationPartitions()[rank];
        MPI_Comm new_comm;
        // Split a communicator into multiple, non-overlapping communicators by color(each destiation partition has its color)
        // 1. Use MPI_Allgather to get the color and key from each process
        // 2. Count the number of processes with the same color; create a
        //    communicator with that many processes.  If this process has
        //    MPI_UNDEFINED as the color, create a process with a single member.
        // 3. Use key to order the ranks
        MPI_Comm_split(MPI_COMM_WORLD, color, rank, &new_comm);
        return new_comm;
    }
    else
    {
        FatalError("NO DESTINATION PARTIONS", AMGX_ERR_CORE);
    }
}

//function object (functor) for thrust calls (it is a unary operator to add a constant)
template<typename T>
class add_constant_op
{
        const T c;
    public:
        add_constant_op(T _c) : c(_c) {}
        __host__ __device__ T operator()(const T &x) const
        {
            return x + c;
        }
};

template<class TConfig>
int create_part_offsets(MPI_Comm &mpicm, Matrix<TConfig> &nv_mtx)
{
    /* WARNING: Notice that part_offsets_h & part_offsets have type int64_t.
                Therefore we need to use MPI_INT64_T (or MPI_LONG_LONG) in MPI_Allgather.
                Also, we need the send & recv buffers to be of the same type, therefore
                we will create a temporary variable n64 of the correct type below. */
    //create TConfig64, which is the same as TConfig, but with index type being int64_t
    typedef typename TConfig::template setVecPrec<AMGX_vecInt64>::Type TConfig64;
    typedef typename TConfig64::VecPrec t_VecPrec; //t_VecPrec = int64_t
    int n, offset, mpist;
    int nranks = 0; //nv_mtx.manager->get_num_partitions();

    if (nv_mtx.manager != NULL)
    {
        //some initializations
        nv_mtx.getOffsetAndSizeForView(OWNED, &offset, &n);
        MPI_Comm_size(mpicm, &nranks);
        nv_mtx.manager->part_offsets_h.resize(nranks + 1);
        //gather the number of rows per partition on the host (on all ranks)
        t_VecPrec n64 = n;
        nv_mtx.manager->part_offsets_h[0] = 0; //first element is zero (the # of rows is gathered afterwards)

        if (typeid(t_VecPrec) == typeid(int64_t))
        {
            mpist = MPI_Allgather(&n64, 1, MPI_INT64_T, nv_mtx.manager->part_offsets_h.raw() + 1, 1, MPI_INT64_T, mpicm);
        }
        else
        {
            FatalError("MPI_Gatherv of the vector has failed - incorrect vector data type", AMGX_ERR_CORE);
        }

        if (mpist != MPI_SUCCESS)
        {
            FatalError("MPI_Gatherv of the vector has failed - detected incorrect MPI return code", AMGX_ERR_CORE);
        }

        //perform a prefix sum
        thrust::inclusive_scan(nv_mtx.manager->part_offsets_h.begin(), nv_mtx.manager->part_offsets_h.end(), nv_mtx.manager->part_offsets_h.begin());
        //create the corresponding array on device (this is important)
        nv_mtx.manager->part_offsets.resize(nranks + 1);
        thrust::copy(nv_mtx.manager->part_offsets_h.begin(), nv_mtx.manager->part_offsets_h.end(), nv_mtx.manager->part_offsets.begin());
    }

    return 0;
}

template<class TConfig>
int glue_matrices(Matrix<TConfig> &nv_mtx, MPI_Comm &nv_mtx_com, MPI_Comm &temp_com)
{
    typedef typename TConfig::IndPrec t_IndPrec;
    typedef typename TConfig::MatPrec t_MatPrec;
    int n, nnz, offset, l, k = 0, i;
    int start, end, shift;
    int mpist, root = 0;
    //MPI call parameters
    t_IndPrec *rc_ptr, *di_ptr;
    t_IndPrec *hli_ptr, *hgi_ptr, *hgr_ptr, *i_ptr, *r_ptr;
    t_MatPrec *hlv_ptr, *hgv_ptr, *v_ptr;
    thrust::host_vector<t_IndPrec> rc;
    thrust::host_vector<t_IndPrec> di;
    //unpacked local matrix on the device and host
    device_vector_alloc<t_IndPrec> Bp;
    device_vector_alloc<t_IndPrec> Bi;
    device_vector_alloc<t_MatPrec> Bv;
    thrust::host_vector<t_IndPrec> hBp;
    thrust::host_vector<t_IndPrec> hBi;
    thrust::host_vector<t_MatPrec> hBv;
    //Consolidated matrices on the host
    thrust::host_vector<t_IndPrec> hAp;
    thrust::host_vector<t_IndPrec> hAi;
    thrust::host_vector<t_MatPrec> hAv;
    //Consolidated matrices on the device
    device_vector_alloc<t_IndPrec> Ap;
    device_vector_alloc<t_IndPrec> Ai;
    device_vector_alloc<t_MatPrec> Av;
    //WARNING: this routine currently supports matrix only with block size =1 (it can be generalized in the future, though)
    //initialize the defaults
    mpist = MPI_SUCCESS;

    if (nv_mtx.manager != NULL)
    {
        //int rank = nv_mtx.manager->global_id();
        nv_mtx.getOffsetAndSizeForView(OWNED, &offset, &n);
        nv_mtx.getNnzForView(OWNED, &nnz);

        if (nv_mtx.manager->part_offsets_h.size() == 0 || nv_mtx.manager->part_offsets.size() == 0)   // create part_offsets_h & part_offsets
        {
            create_part_offsets(nv_mtx_com, nv_mtx);  // (if needed for aggregation path)
        }

        Bp.resize(n + 1);
        Bi.resize(nnz);
        Bv.resize(nnz);
        hBp.resize(n + 1);
        hBi.resize(nnz);
        hBv.resize(nnz);
        //--- unpack the matrix ---
        nv_mtx.manager->unpack_partition(thrust::raw_pointer_cast(Bp.data()),
                                         thrust::raw_pointer_cast(Bi.data()),
                                         thrust::raw_pointer_cast(Bv.data()));
        cudaCheckError();
        //copy to host (should be able to optimize this out later on)
        hBp = Bp;
        hBi = Bi;
        hBv = Bv;
        cudaCheckError();

        // --- Glue matrices ---

        // construct global row pointers
        // compute recvcounts and displacements for MPI_Gatherv
        if (nv_mtx.manager->isRootPartition())
        {
            l = nv_mtx.manager->getNumPartsToConsolidate();      // number of partitions
            rc.resize(l);
            di.resize(l);

            //compute recvcounts and displacements for MPI_Gatherv
            for (i = 0; i < l; i++)
            {
                start = nv_mtx.manager->part_offsets_h[nv_mtx.manager->getPartsToConsolidate()[i]];
                end   = nv_mtx.manager->part_offsets_h[nv_mtx.manager->getPartsToConsolidate()[i] + 1];
                rc[i] = end - start;
                di[i] = k + 1;
                k += rc[i];
            }

            hAp.resize(k + 1); // extra +1 is needed because row_offsets have one extra element at the end
        }

        cudaCheckError();
        //alias raw pointers to thrust vector data (see thrust example unwrap_pointer for details)
        rc_ptr = thrust::raw_pointer_cast(rc.data());
        di_ptr = thrust::raw_pointer_cast(di.data());
        hli_ptr = thrust::raw_pointer_cast(hBp.data());
        hgr_ptr = thrust::raw_pointer_cast(hAp.data());
        cudaCheckError();

        //gather (on the host)
        if (typeid(t_IndPrec) == typeid(int))
        {
            mpist = MPI_Gatherv(hli_ptr + 1, n, MPI_INT,  hgr_ptr, rc_ptr, di_ptr, MPI_INT, root, temp_com);
        }
        else
        {
            FatalError("MPI_Gatherv of the vector has failed - incorrect vector data type", AMGX_ERR_CORE);
        }

        if (mpist != MPI_SUCCESS)
        {
            FatalError("MPI_Gatherv of the vector has failed - detected incorrect MPI return code", AMGX_ERR_CORE);
        }

        // Adjust row pointers, construct global column indices and values (recvcounts and displacements were computed above)
        if (nv_mtx.manager->isRootPartition())
        {
            //adjust global row pointers and setup the recvcounts & displacements for subsequent MPI calls
            for (i = 0; i < l; i++)
            {
                start = di[i] - 1;
                end   = di[i] + rc[i] - 1;
                shift = hAp[start];
                thrust::transform(hAp.begin() + start + 1, hAp.begin() + end + 1, hAp.begin() + start + 1, add_constant_op<t_IndPrec>(shift));
                cudaCheckError();
                di[i] = shift;
                rc[i] = hAp[end] - hAp[start];
            }

            //some allocations/resizing
            hAi.resize(hAp[k]);
            hAv.resize(hAp[k]);
        }

        //alias raw pointers to thrust vector data (see thrust example unwrap_pointer for details)
        rc_ptr = thrust::raw_pointer_cast(rc.data());
        di_ptr = thrust::raw_pointer_cast(di.data());
        hli_ptr = thrust::raw_pointer_cast(hBi.data());
        hgi_ptr = thrust::raw_pointer_cast(hAi.data());
        hlv_ptr = thrust::raw_pointer_cast(hBv.data());
        hgv_ptr = thrust::raw_pointer_cast(hAv.data());
        cudaCheckError();

        //gather (on the host)
        //columns indices
        if (typeid(t_IndPrec) == typeid(int))
        {
            mpist = MPI_Gatherv(hli_ptr, nnz, MPI_INT,  hgi_ptr, rc_ptr, di_ptr, MPI_INT,  root, temp_com);
        }
        else
        {
            FatalError("MPI_Gatherv of the vector has failed - incorrect vector data type", AMGX_ERR_CORE);
        }

        if (mpist != MPI_SUCCESS)
        {
            FatalError("MPI_Gatherv of the vector has failed - detected incorrect MPI return code", AMGX_ERR_CORE);
        }

        //values
        if      (typeid(t_MatPrec) == typeid(float))
        {
            mpist = MPI_Gatherv(hlv_ptr, nnz, MPI_FLOAT,  hgv_ptr, rc_ptr, di_ptr, MPI_FLOAT,  root, temp_com);
        }
        else if (typeid(t_MatPrec) == typeid(double))
        {
            mpist = MPI_Gatherv(hlv_ptr, nnz, MPI_DOUBLE, hgv_ptr, rc_ptr, di_ptr, MPI_DOUBLE, root, temp_com);
        }
        else
        {
            FatalError("MPI_Gatherv of the vector has failed - incorrect vector data type", AMGX_ERR_CORE);
        }

        if (mpist != MPI_SUCCESS)
        {
            FatalError("MPI_Gatherv of the vector has failed - detected incorrect MPI return code", AMGX_ERR_CORE);
        }

        // --- Upload matrices ---
        if (nv_mtx.manager->isRootPartition())
        {
            n = hAp.size() - 1;
            nnz = hAi.size();
            Ap.resize(hAp.size());
            Ai.resize(hAi.size());
            Av.resize(hAv.size());
            thrust::copy(hAp.begin(), hAp.end(), Ap.begin());
            thrust::copy(hAi.begin(), hAi.end(), Ai.begin());
            thrust::copy(hAv.begin(), hAv.end(), Av.begin());
            cudaCheckError();
        }
        else
        {
            n = 0;
            nnz  = 0;
            Ap.resize(1); // warning row_ponter size is expected to be n+1.
            Ap.push_back(0);
            Ai.resize(0);
            Av.resize(0);
            cudaCheckError();
        }

        r_ptr = thrust::raw_pointer_cast(Ap.data());
        i_ptr = thrust::raw_pointer_cast(Ai.data());
        v_ptr = thrust::raw_pointer_cast(Av.data());
        cudaCheckError();
        upload_matrix_after_glue(n, nnz, r_ptr, i_ptr, v_ptr, nv_mtx);
    }
    else
    {
        /* ASSUMPTION: when manager has not been allocated you are running on a single rank */
    }

    return 0;
}

template<class TConfig>
int upload_matrix_after_glue(int n, int nnz, int *r_ptr, int *i_ptr, void *v_ptr, Matrix<TConfig> &nv_mtx)
{
    // Using a path similar to AMGX_matrix_upload_all_global
    typedef typename TConfig::IndPrec t_IndPrec;
    typedef typename TConfig::MatPrec t_MatPrec;
    typedef typename TConfig::template setMemSpace<AMGX_host>::Type TConfig_h;
    typedef typename TConfig::template setVecPrec<AMGX_vecInt>::Type ivec_value_type;
    typedef typename TConfig_h::template setVecPrec<AMGX_vecInt>::Type ivec_value_type_h;
    typedef typename ivec_value_type_h::VecPrec VecInt_t;
    typedef Vector<ivec_value_type> IVector;
    // some parameters
    int block_dimx, block_dimy, num_ranks, n_global, start, end, val;
    t_IndPrec *part_vec_ptr;
    thrust::host_vector<t_IndPrec> pv;
    // set parameters
    nv_mtx.setView(ALL); // not sure about this
    n_global = nv_mtx.manager->part_offsets_h.back();
    block_dimx = nv_mtx.get_block_dimx();
    block_dimy = nv_mtx.get_block_dimy();
    //MPI_Comm* mpi_comm = nv_mtx.getResources()->getMpiComm();
    //MPI_Comm_size(*mpi_comm, &num_ranks);
    num_ranks = nv_mtx.manager->getComms()->get_num_partitions();
    // WARNING We create an artificial partition vectior that matches the new distribution
    // example n = 8, num_ranks(ie. num partitions) = 4 , DestinationPartitions[0,0,2,2], partvec = [0,0,0,0,2,2,2,2]
    // This might be an issue for the finest level if input_partvect !=NULL
    Vector<ivec_value_type_h> dest_partitions = nv_mtx.manager->getDestinationPartitions();

    for (int i = 0; i < num_ranks; i++)
    {
        val = dest_partitions[i];
        start = nv_mtx.manager->part_offsets_h[i];
        end   = nv_mtx.manager->part_offsets_h[i + 1];

        for (int j = 0; j < end - start; j++)
        {
            pv.push_back(val);
        }
    }

    part_vec_ptr = thrust::raw_pointer_cast(pv.data());
    cudaCheckError();
    // Save some glue info
    bool is_root_partition = nv_mtx.manager->isRootPartition();
    int dest_part = nv_mtx.manager->getMyDestinationPartition();
    int num_parts_to_consolidate = nv_mtx.manager->getNumPartsToConsolidate();
    Vector<ivec_value_type_h> parts_to_consolidate = nv_mtx.manager->getPartsToConsolidate();
    std::vector<int> cao;

    for (int i = 0; i < nv_mtx.manager->part_offsets_h.size(); i++)
    {
        cao.push_back(nv_mtx.manager->part_offsets_h[i]);
    }

    // WARNING
    // renumbering contains the inverse permutation to unreorder an amgx vector
    // inverse_renumbering contains the permutaion to reorder an amgx vector
    Vector<ivec_value_type> ir = nv_mtx.manager->inverse_renumbering;
    Vector<ivec_value_type> r = nv_mtx.manager->renumbering;
    // We need that to exchange the halo of unglued vectors (in coarse level consolidation)
    Vector<ivec_value_type_h> nei = nv_mtx.manager->neighbors;  // just neighbors before glue
    std::vector<std::vector<VecInt_t> > b2lr = nv_mtx.manager->getB2Lrings(); //list of boundary nodes to export to other partitions.
    Vector<ivec_value_type_h> ho = nv_mtx.manager->halo_offsets;
    std::vector<IVector > b2lm = nv_mtx.manager->getB2Lmaps();
    cudaCheckError();

    // Create a fresh distributed manager
    if (nv_mtx.manager != NULL )
    {
        delete nv_mtx.manager;
    }

    nv_mtx.manager = new DistributedManager<TConfig>(nv_mtx);
    nv_mtx.set_initialized(0);
    nv_mtx.delProps(DIAG);
    // Load distributed matrix
    MatrixDistribution mdist;
    mdist.setPartitionVec(part_vec_ptr);
    nv_mtx.manager->loadDistributedMatrix(n, nnz, block_dimx, block_dimy, r_ptr, i_ptr, (t_MatPrec *) v_ptr, num_ranks, n_global, NULL, mdist);
    // Create B2L_maps for comm
    nv_mtx.manager->renumberMatrixOneRing();
    // WARNING WE SHOULD GET THE NUMBER OF RINGS AND DO THE FOLLOWING ONLY IF THERE ARE 2 RINGS
    // Exchange 1 ring halo rows (for d2 interp)
    // if (num_import_rings == 2)
    nv_mtx.manager->createOneRingHaloRows();
    nv_mtx.manager->getComms()->set_neighbors(nv_mtx.manager->num_neighbors());
    nv_mtx.setView(OWNED);
    nv_mtx.set_initialized(1);
    cudaCheckError();
    // restore some glue info to consolidate the vectors in the future
    nv_mtx.manager->setDestinationPartitions(dest_partitions);
    nv_mtx.manager->setIsRootPartition(is_root_partition);
    nv_mtx.manager->setNumPartsToConsolidate(num_parts_to_consolidate);
    nv_mtx.manager->setPartsToConsolidate(parts_to_consolidate);
    nv_mtx.manager->setIsGlued(true);
    nv_mtx.manager->setMyDestinationPartition(dest_part);
    nv_mtx.manager->setConsolidationArrayOffsets(cao); // partions_offest before consolidation
    // set fine level data structures, this is used to match the former distribution when we upload / download from the API

    if (nv_mtx.amg_level_index == 0)
    {
        // just small copies inside fineLevelUpdate.
        nv_mtx.manager->fineLevelUpdate();
    }

    nv_mtx.manager->renumbering_before_glue = r;
    nv_mtx.manager->inverse_renumbering_before_glue = ir;
    nv_mtx.manager->neighbors_before_glue = nei;
    nv_mtx.manager->halo_offsets_before_glue = ho;
    nv_mtx.manager->B2L_rings_before_glue = b2lr;
    nv_mtx.manager->B2L_maps_before_glue = b2lm;
    cudaCheckError();
    return 0;
}

template<class TConfig>
int glue_vector(Matrix<TConfig> &nv_mtx, MPI_Comm &A_comm, Vector<TConfig> &nv_vec, MPI_Comm &temp_com)
{
    // glu vecots based on dest_partitions (which contains partitions should be merged together)
    typedef typename TConfig::IndPrec t_IndPrec;
    typedef typename TConfig::VecPrec t_VecPrec;
    int n, l, mpist, start, end, k = 0, root = 0, rank = 0;
    //MPI call parameters
    t_IndPrec *rc_ptr, *di_ptr;
    t_VecPrec *hv_ptr, *hg_ptr, *v_ptr;
    thrust::host_vector<t_IndPrec> rc;
    thrust::host_vector<t_IndPrec> di;
    //unreordered local vector on the host
    thrust::host_vector<t_VecPrec> hv;
    //constructed global vector on the host
    thrust::host_vector<t_VecPrec> hg;
    //constructed global vector on the device
    device_vector_alloc<t_VecPrec> v;
    //WARNING: this routine currently supports vectors only with block size =1 (it can be generalized in the future, though)
    //initialize the defaults
    mpist = MPI_SUCCESS;

    if (nv_mtx.manager != NULL)
    {
        // some initializations
        rank = nv_mtx.manager->global_id();

        if (nv_mtx.manager->getComms() != NULL)
        {
            nv_mtx.manager->getComms()->get_mpi_comm();
        }

        n = nv_mtx.manager->getConsolidationArrayOffsets()[rank + 1] - nv_mtx.manager->getConsolidationArrayOffsets()[rank];

        if (nv_mtx.manager->getConsolidationArrayOffsets().size() == 0)
        {
            std::cout << "ERROR part_offsets in glue path" << std::endl;
        }

        l = nv_mtx.manager->getNumPartsToConsolidate();      // number of partitions
        //some allocations/resizing
        hv.resize(nv_mtx.manager->renumbering_before_glue.size());                         // host copy of nv_vec

        if (nv_mtx.manager->isRootPartition())
        {
            // This works with neighbours only
            hg.resize(nv_mtx.manager->getConsolidationArrayOffsets()[rank + l] - nv_mtx.manager->getConsolidationArrayOffsets()[rank]); // host copy of cvec
            rc.resize(l);
            di.resize(l);
        }

        cudaCheckError();
        //--- unreorder the vector back (just like you did with the matrix, but only need to undo the interior-boundary reordering, because others do not apply) ---
        // unreorder and copy the vector
        // WARNING
        // renumbering contains the inverse permutation to unreorder an amgx vector
        // inverse_renumbering contains the permutaion to reorder an amgx vector
        thrust::copy(thrust::make_permutation_iterator(nv_vec.begin(), nv_mtx.manager->renumbering_before_glue.begin()  ),
                     thrust::make_permutation_iterator(nv_vec.begin(), nv_mtx.manager->renumbering_before_glue.begin() +  nv_mtx.manager->renumbering_before_glue.size()),
                     hv.begin());
        cudaCheckError();
        hv.resize(n);

        // --- construct global vector (rhs/sol) ---
        //compute recvcounts and displacements for MPI_Gatherv
        if (nv_mtx.manager->isRootPartition())
        {
            l = nv_mtx.manager->getNumPartsToConsolidate();      // number of partitions

            //compute recvcounts and displacements for MPI_Gatherv
            for (int i = 0; i < l; i++)
            {
                start = nv_mtx.manager->getConsolidationArrayOffsets()[nv_mtx.manager->getPartsToConsolidate()[i]];
                end   = nv_mtx.manager->getConsolidationArrayOffsets()[nv_mtx.manager->getPartsToConsolidate()[i] + 1];
                rc[i] = end - start;
                di[i] = k;
                k += rc[i];
            }
        }

        //alias raw pointers to thrust vector data (see thrust example unwrap_pointer for details)
        rc_ptr = thrust::raw_pointer_cast(rc.data());
        di_ptr = thrust::raw_pointer_cast(di.data());
        hv_ptr = thrust::raw_pointer_cast(hv.data());
        hg_ptr = thrust::raw_pointer_cast(hg.data());
        cudaCheckError();

        //gather (on the host)
        if      (typeid(t_VecPrec) == typeid(float))
        {
            mpist = MPI_Gatherv(hv_ptr, n, MPI_FLOAT,  hg_ptr, rc_ptr, di_ptr, MPI_FLOAT,  root, temp_com);
        }
        else if (typeid(t_VecPrec) == typeid(double))
        {
            mpist = MPI_Gatherv(hv_ptr, n, MPI_DOUBLE, hg_ptr, rc_ptr, di_ptr, MPI_DOUBLE, root, temp_com);
        }
        else
        {
            FatalError("MPI_Gatherv of the vector has failed - incorrect vector data type", AMGX_ERR_CORE);
        }

        if (mpist != MPI_SUCCESS)
        {
            FatalError("MPI_Gatherv of the vector has failed - detected incorrect MPI return code", AMGX_ERR_CORE);
        }

        // clean
        nv_vec.in_transfer = IDLE;

        //nv_vec.dirtybit = 0;
        if (nv_vec.buffer != NULL)
        {
            delete nv_vec.buffer;
            nv_vec.buffer = NULL;
            nv_vec.buffer_size = 0;
        }

        if (nv_vec.linear_buffers_size != 0)
        {
            amgx::memory::cudaFreeHost(&(nv_vec.linear_buffers[0]));
            nv_vec.linear_buffers_size = 0;
        }

        if (nv_vec.explicit_host_buffer)
        {
            amgx::memory::cudaFreeHost(nv_vec.explicit_host_buffer);
            nv_vec.explicit_host_buffer = NULL;
            nv_vec.explicit_buffer_size = 0;
            cudaEventDestroy(nv_vec.mpi_event);
        }

        // resize
        if (nv_mtx.manager->isRootPartition())
        {
            n = hg.size();
            v.resize(hg.size());
            thrust::copy(hg.begin(), hg.end(), v.begin());
            cudaCheckError();
        }
        else
        {
            n = 0;
            v.resize(0);
            cudaCheckError();
        }

        // upload
        v_ptr = thrust::raw_pointer_cast(v.data());
        cudaCheckError();
        upload_vector_after_glue(n, v_ptr, nv_vec, nv_mtx);
    }
    else
    {
        // ASSUMPTION: when manager has not been allocated you are running on a single rank
    }

    return 0;
}

template<class TConfig>
int upload_vector_after_glue(int n, void *v_ptr, Vector<TConfig> &nv_vec, Matrix<TConfig> &nv_mtx)
{
    typedef typename TConfig::VecPrec t_VecPrec;
    // vector bind
    nv_vec.unsetManager();
    cudaCheckError();

    if (nv_mtx.manager != NULL)
    {
        nv_vec.setManager(*(nv_mtx.manager));
    }

    cudaCheckError();

    if (nv_vec.is_transformed())
    {
        nv_vec.unset_transformed();
    }

    nv_vec.set_block_dimx(1);
    nv_vec.set_block_dimy(nv_mtx.get_block_dimy());
    // the dirtybit has to be set to one here in order to have correct results to ensure an exachange halo before the solve
    // this is particulary important when the number of consolidated partitions is greater than 1 on large matrices such as drivaer9M
    nv_vec.dirtybit = 1;

    if (nv_mtx.manager != NULL)
    {
        nv_vec.getManager()->transformAndUploadVector(nv_vec, (t_VecPrec *)v_ptr, n, nv_vec.get_block_dimy());
    }

    MPI_Barrier(MPI_COMM_WORLD);
    return 0;
}

template<class TConfig>
int unglue_vector(Matrix<TConfig> &nv_mtx, MPI_Comm &A_comm, Vector<TConfig> &nv_vec, MPI_Comm &temp_com, Vector<TConfig> &nv_vec_unglued)
{
    // glue vecots based on dest_partitions (which contains partitions should be merged together)
    typedef typename TConfig::IndPrec t_IndPrec;
    typedef typename TConfig::VecPrec t_VecPrec;
    int n_loc, l, mpist, start, end, k = 0, root = 0, rank = 0;
    //MPI call parameters
    t_IndPrec *sc_ptr, *di_ptr;
    t_VecPrec *hv_ptr, *hg_ptr;
    thrust::host_vector<t_IndPrec> sc;
    thrust::host_vector<t_IndPrec> di;
    //unreordered local vector on the host
    thrust::host_vector<t_VecPrec> hv;
    //constructed global vector on the host
    thrust::host_vector<t_VecPrec> hg;
    //constructed global vector on the device
    device_vector_alloc<t_VecPrec> v;
    //WARNING: this routine currently supports vectors only with block size =1 (it can be generalized in the future, though)
    //initialize the defaults
    mpist = MPI_SUCCESS;

    if (nv_mtx.manager != NULL)
    {
        // some initializations
        rank = nv_mtx.manager->global_id();

        if (nv_mtx.manager->getComms() != NULL)
        {
            nv_mtx.manager->getComms()->get_mpi_comm();
        }

        n_loc = nv_mtx.manager->getConsolidationArrayOffsets()[rank + 1] - nv_mtx.manager->getConsolidationArrayOffsets()[rank];

        if (nv_mtx.manager->getConsolidationArrayOffsets().size() == 0)
        {
            printf("ERROR part_offsets\n");
        }

        l = nv_mtx.manager->getNumPartsToConsolidate();      // number of partitions
        //some allocations/resizing
        hv.resize(n_loc);                         // host copy

        if (nv_mtx.manager->isRootPartition())
        {
            hg.resize(nv_vec.size()); // host copy of cvec
        }

        sc.resize(l);
        di.resize(l);
        cudaCheckError();
        // Exchange_halo before unreordering
        // Do we need that?
        nv_mtx.manager->exchange_halo(nv_vec, nv_vec.tag);

        // unreorder the vector
        if (nv_mtx.manager->isRootPartition())
        {
            // WARNING
            // renumbering contains the inverse permutation to unreorder an amgx vector
            // inverse_renumbering contains the permutaion to reorder an amgx vector
            thrust::copy(thrust::make_permutation_iterator(nv_vec.begin(), nv_mtx.manager->renumbering.begin()  ),
                         thrust::make_permutation_iterator(nv_vec.begin(), nv_mtx.manager->renumbering.begin() + nv_mtx.manager->renumbering.size()),
                         hg.begin());
            cudaCheckError();
            hg.resize(nv_mtx.manager->getConsolidationArrayOffsets()[rank + l] - nv_mtx.manager->getConsolidationArrayOffsets()[rank]);
        }

        // --- construct local vector (sol) ---
        //compute sendcounts and displacements for MPI_Gatherv
        for (int i = 0; i < l; i++)
        {
            start = nv_mtx.manager->getConsolidationArrayOffsets()[nv_mtx.manager->getPartsToConsolidate()[i]];
            end   = nv_mtx.manager->getConsolidationArrayOffsets()[nv_mtx.manager->getPartsToConsolidate()[i] + 1];
            sc[i] = end - start;
            di[i] = k;
            k += sc[i];
        }

        //alias raw pointers to thrust vector data (see thrust example unwrap_pointer for details)
        sc_ptr = thrust::raw_pointer_cast(sc.data());
        di_ptr = thrust::raw_pointer_cast(di.data());
        hv_ptr = thrust::raw_pointer_cast(hv.data());
        hg_ptr = thrust::raw_pointer_cast(hg.data());
        cudaCheckError();

        // Scatter (on the host)
        if      (typeid(t_VecPrec) == typeid(float))
        {
            mpist = MPI_Scatterv(hg_ptr, sc_ptr, di_ptr, MPI_FLOAT,  hv_ptr, n_loc, MPI_FLOAT,  root, temp_com);
        }
        else if (typeid(t_VecPrec) == typeid(double))
        {
            mpist = MPI_Scatterv(hg_ptr, sc_ptr, di_ptr, MPI_DOUBLE, hv_ptr, n_loc, MPI_DOUBLE, root, temp_com);
        }
        else
        {
            FatalError("MPI_Gatherv of the vector has failed - incorrect vector data type", AMGX_ERR_CORE);
        }

        if (mpist != MPI_SUCCESS)
        {
            FatalError("MPI_Gatherv of the vector has failed - detected incorrect MPI return code", AMGX_ERR_CORE);
        }

        // --- Manual upload ---
        // Cleaning
        nv_vec_unglued.in_transfer = IDLE;

        if (nv_vec_unglued.buffer != NULL)
        {
            delete nv_vec_unglued.buffer;
            nv_vec_unglued.buffer = NULL;
            nv_vec_unglued.buffer_size = 0;
        }

        if (nv_vec_unglued.linear_buffers_size != 0)
        {
            amgx::memory::cudaFreeHost(&(nv_vec_unglued.linear_buffers[0]));
            nv_vec_unglued.linear_buffers_size = 0;
        }

        if (nv_vec_unglued.explicit_host_buffer)
        {
            amgx::memory::cudaFreeHost(nv_vec_unglued.explicit_host_buffer);
            nv_vec_unglued.explicit_host_buffer = NULL;
            nv_vec_unglued.explicit_buffer_size = 0;
            cudaEventDestroy(nv_vec_unglued.mpi_event);
        }

        // We should avoid copies between nv_vec and hv here
        nv_vec_unglued.resize( nv_mtx.manager->inverse_renumbering_before_glue.size());
        thrust::fill( nv_vec_unglued.begin(), nv_vec_unglued.end(), 0.0 );
        thrust::copy(hv.begin(), hv.end(), nv_vec_unglued.begin());
        hv.resize( nv_mtx.manager->inverse_renumbering_before_glue.size());
        cudaCheckError();
        //  Manual reordering
        //  Upload_vector_after_glue is not going to work because matrix managers has been modified during glued matrices, and don't match the new, glued, topology.
        thrust::copy(thrust::make_permutation_iterator(nv_vec_unglued.begin(), nv_mtx.manager->inverse_renumbering_before_glue.begin()  ),
                     thrust::make_permutation_iterator(nv_vec_unglued.begin(), nv_mtx.manager->inverse_renumbering_before_glue.begin() + nv_mtx.manager->inverse_renumbering_before_glue.size()),
                     hv.begin());
        cudaCheckError();
        thrust::fill( nv_vec_unglued.begin(), nv_vec_unglued.end(), 0.0 );
        thrust::copy(hv.begin(), hv.end(), nv_vec_unglued.begin());
    }
    else
    {
        // ASSUMPTION: when manager has not been allocated you are running on a single rank
        printf("Glue was called on a single rank\n");
    }

    return 0;
}

#if 0

// The folowing code is to perform an exhange halo using data that doesn't matches the topology of the matrix stored in its distributed manager
// We use instead other containers stored in the distributed manager. They are suffixed by "_before_glue"
// This allows to exchange vector halo between unglued vectors from glued matrices

template <class TConfig>
void exchange_halo_after_unglue(const Matrix<TConfig> &A, Vector<TConfig>  &data, int tag, int num_ring = 1)
{
    setup_after_unglue(data, A, num_ring);  //set pointers to buffer
    gather_B2L_after_unglue(A, data, num_ring);             //write values to buffer
    exchange_halo_after_unglue(data, A, num_ring); //exchange buffers
    //scatter_L2H(data);            //NULL op
}
/*
template <class TConfig>
void CommsMPIHostBufferStream<T_Config>::setup(DVector &b, const Matrix<TConfig> &m, int num_rings) { do_setup_after_unglue((b, m, num_rings);}
template <class T_Config>
void CommsMPIHostBufferStream<T_Config>::exchange_halo(DVector &b, const Matrix<TConfig> &m, cudaEvent_t event, int tag, int num_rings) {  do_exchange_halo_after_unglue((b, m, num_rings);}
template <class T_Config>
void CommsMPIHostBufferStream<T_Config>::setup(FVector &b, const Matrix<TConfig> &m, int tag, int num_rings) { do_setup_after_unglue((b, m, num_rings);}
template <class T_Config>
void CommsMPIHostBufferStream<T_Config>::exchange_halo(FVector &b, const Matrix<TConfig> &m, cudaEvent_t event, int tag, int num_rings) {  do_exchange_halo_after_unglue((b, m, num_rings);}
*/
template <class TConfig>
void setup_after_unglue(Vector<TConfig> &b, const Matrix<TConfig> &m, int num_rings)
{
    /*
    thrust::copy( m.manager->neighbors_before_glue.begin(),  m.manager->neighbors_before_glue.end(), std::ostream_iterator<int64_t>(std::cout, " "));
    thrust::copy( m.manager->halo_offsets_before_glue.begin(),  m.manager->halo_offsets_before_glue.end(), std::ostream_iterator<int64_t>(std::cout, " "));
    for (int i = 0; i < m.manager->B2L_rings_before_glue.size(); ++i)
    {
      thrust::copy( m.manager->B2L_rings_before_glue[i].begin(),  m.manager->B2L_rings_before_glue[i].end(), std::ostream_iterator<int64_t>(std::cout, " "));
    }

    for (int i = 0; i < m.manager->B2L_maps_before_glue.size(); ++i)
    {
      thrust::copy( m.manager->B2L_maps_before_glue[i].begin(),  m.manager->B2L_maps_before_glue[i].end(), std::ostream_iterator<int64_t>(std::cout, " "));
    }
    */
    if (TConfig::memSpace == AMGX_host)
    {
        FatalError("MPI Comms module no implemented for host", AMGX_ERR_NOT_IMPLEMENTED);
    }
    else
    {
#ifdef AMGX_WITH_MPI
        int bsize = b.get_block_size();
        int num_cols = b.get_num_cols();

        if (bsize != 1 && num_cols != 1)
            FatalError("Error: vector cannot have block size and subspace size.",
                       AMGX_ERR_INTERNAL);

        // set num neighbors = size of B2L_rings_before_glue
        // need to do this because comms might have more neighbors than our matrix knows about
        int neighbors = m.manager->B2L_rings_before_glue.size();
        m.manager->getComms()->set_neighbors(m.manager->B2L_rings_before_glue.size());

        if (b.in_transfer & SENDING)
        {
            b.in_transfer = IDLE;
        }

        typedef typename TConfig::template setVecPrec<(AMGX_VecPrecision)AMGX_GET_MODE_VAL(AMGX_MatPrecision, TConfig::mode)>::Type value_type;
        b.requests.resize(2 * neighbors); //first part is sends second is receives
        b.statuses.resize(2 * neighbors);

        for (int i = 0; i < 2 * neighbors; i++)
        {
            b.requests[i] = MPI_REQUEST_NULL;
        }

        int total = 0;

        for (int i = 0; i < neighbors; i++)
        {
            total += m.manager->B2L_rings_before_glue[i][num_rings] * bsize * num_cols;
        }

        b.buffer_size = total;

        if (b.buffer == NULL)
        {
            b.buffer = new Vector<TConfig>(total);
        }
        else
        {
            if (total > b.buffer->size())
            {
                b.buffer->resize(total);
            }
        }

        if (b.linear_buffers_size < neighbors)
        {
            if (b.linear_buffers_size != 0) { amgx::memory::cudaFreeHost(b.linear_buffers); }

            amgx::memory::cudaMallocHost((void **) & (b.linear_buffers), neighbors * sizeof(value_type *));
            b.linear_buffers_size = neighbors;
        }

        cudaCheckError();
        total = 0;
        bool linear_buffers_changed = false;

        for (int i = 0; i < neighbors; i++)
        {
            if (b.linear_buffers[i] != b.buffer->raw() + total)
            {
                linear_buffers_changed = true;
            }

            b.linear_buffers[i] = b.buffer->raw() + total;
            total += m.manager->B2L_rings_before_glue[i][num_rings] * bsize * num_cols;
        }

        // Copy to device
        if (linear_buffers_changed)
        {
            b.linear_buffers_ptrs.resize(neighbors);
            //thrust::copy(b.linear_buffers.begin(),b.linear_buffers.end(),b.linear_buffers_ptrs.begin());
            cudaMemcpyAsync(thrust::raw_pointer_cast(&b.linear_buffers_ptrs[0]), &(b.linear_buffers[0]), neighbors * sizeof(value_type *), cudaMemcpyHostToDevice);
            cudaCheckError();
        }

        int size = 0;
        size  = total + (m.manager->halo_offsets_before_glue[num_rings * neighbors] - m.manager->halo_offsets_before_glue[0]) * bsize * num_cols;

        if (size > 0)
        {
            if (b.explicit_host_buffer == NULL)
            {
                b.host_buffer.resize(1);
                cudaEventCreateWithFlags(&b.mpi_event, cudaEventDisableTiming);
                cudaCheckError();
                amgx::memory::cudaMallocHost((void **)&b.explicit_host_buffer, size * sizeof(value_type));
                cudaCheckError();
            }
            else if (size > b.explicit_buffer_size)
            {
                amgx::memory::cudaFreeHost(b.explicit_host_buffer);
                cudaCheckError();
                amgx::memory::cudaMallocHost((void **)&b.explicit_host_buffer, size * sizeof(value_type));
                cudaCheckError();
            }

            cudaCheckError();
            b.explicit_buffer_size = size;
        }

#else
        FatalError("MPI Comms module requires compiling with MPI", AMGX_ERR_NOT_IMPLEMENTED);
#endif
    }
}
template <class TConfig>
void gather_B2L_after_unglue(const Matrix<TConfig> &m, Vector<TConfig> &b, int num_rings = 1)
{
    if (TConfig::memSpace == AMGX_host)
    {
        if (m.manager->neighbors_before_glue.size() > 0)
        {
            FatalError("Distributed solve only supported on devices", AMGX_ERR_NOT_IMPLEMENTED);
        }
    }
    else
    {
        for (int i = 0; i < m.manager->neighbors_before_glue.size(); i++)
        {
            int size = m.manager->B2L_rings_before_glue[i][num_rings];
            int num_blocks = min(4096, (size + 127) / 128);

            if ( size != 0)
            {
                if (b.get_num_cols() == 1)
                {
                    gatherToBuffer <<< num_blocks, 128>>>(b.raw(), m.manager->B2L_maps_before_glue[i].raw(), b.linear_buffers[i], b.get_block_size(), size);
                }
                else
                {
                    gatherToBufferMultivector <<< num_blocks, 128>>>(b.raw(), m.manager->B2L_maps_before_glue[i].raw(), b.linear_buffers[i], b.get_num_cols(), b.get_lda(), size);
                }

                cudaCheckError();
            }
        }
    }
}
template <class TConfig>
void exchange_halo_after_unglue(Vector<TConfig> &b, const Matrix<TConfig> &m, int num_rings)
{
    if (TConfig::memSpace == AMGX_host)
    {
        FatalError("Halo exchanges not implemented for host", AMGX_ERR_NOT_IMPLEMENTED);
    }
    else
    {
#ifdef AMGX_WITH_MPI
        typedef typename TConfig::VecPrec VecPrec;
        cudaCheckError();
        int bsize = b.get_block_size();
        int num_cols = b.get_num_cols();
        int offset = 0;
        int neighbors = m.manager->B2L_rings_before_glue.size();
        MPI_Comm mpi_comm = m.manager->getComms()->get_mpi_comm();

        if (b.buffer_size != 0)
        {
            cudaMemcpy(&(b.explicit_host_buffer[0]), b.buffer->raw(), b.buffer_size * sizeof(typename TConfig::VecPrec), cudaMemcpyDeviceToHost);
        }

        for (int i = 0; i < neighbors; i++)
        {
            int size = m.manager->B2L_rings_before_glue[i][num_rings] * bsize * num_cols;

            if (size != 0)
            {
                MPI_Isend(&(b.explicit_host_buffer[offset]), size * sizeof(typename TConfig::VecPrec), MPI_BYTE, m.manager->neighbors_before_glue[i], m.manager->global_id(), mpi_comm, &b.requests[i]);
            }
            else
            {
                MPI_Isend(&(b.host_buffer[0]), size * sizeof(typename TConfig::VecPrec), MPI_BYTE, m.manager->neighbors_before_glue[i], m.manager->global_id(), mpi_comm, &b.requests[i]);
            }

            offset += size;
        }

        b.in_transfer = RECEIVING | SENDING;
        offset = 0;

        for (int i = 0; i < neighbors; i++)
        {
            // Count total size to receive from one neighbor
            int size = 0;

            for (int j = 0; j < num_rings; j++)
            {
                size += m.manager->halo_offsets_before_glue[j * neighbors + i + 1] * bsize * num_cols - m.manager->halo_offsets_before_glue[j * neighbors + i] * bsize * num_cols;
            }

            if (size != 0)
            {
                MPI_Irecv(&(b.explicit_host_buffer[b.buffer_size + offset]), size * sizeof(typename TConfig::VecPrec), MPI_BYTE, m.manager->neighbors_before_glue[i], m.manager->neighbors_before_glue[i], mpi_comm, &b.requests[neighbors + i]);
            }
            else
            {
                MPI_Irecv(&(b.host_buffer[0]), size * sizeof(typename TConfig::VecPrec), MPI_BYTE, m.manager->neighbors_before_glue[i], m.manager->neighbors_before_glue[i], mpi_comm, &b.requests[neighbors + i]);
            }

            offset += size;
            int required_size = m.manager->halo_offsets_before_glue[0] * bsize * num_cols + offset;

            if (required_size > b.size())
            {
                // happen because we have 2 ring
                // required_size correspond to "n" in the FULL view of the unconsolidated matrix.
                // In exchange halo this is a fatal error since it should never happen.
                b.resize(required_size);
            }
        }

        MPI_Waitall(2 * neighbors, &b.requests[0], /*&b.statuses[0]*/ MPI_STATUSES_IGNORE); //I only wait to receive data, I can start working before all my buffers were sent
        b.dirtybit = 0;
        b.in_transfer = IDLE;

        // copy on host ring by ring
        if (num_rings == 1)
        {
            if (num_cols == 1)
            {
                if (offset != 0)
                {
                    cudaMemcpy(b.raw() + m.manager->halo_offsets_before_glue[0]*bsize, &(b.explicit_host_buffer[b.buffer_size]), offset * sizeof(typename TConfig::VecPrec), cudaMemcpyHostToDevice);
                }
            }
            else
            {
                int lda = b.get_lda();
                VecPrec *rank_start = &(b.explicit_host_buffer[b.buffer_size]);

                for (int i = 0; i < neighbors; ++i)
                {
                    int halo_size = m.manager->halo_offsets_before_glue[i + 1] - m.manager->halo_offsets_before_glue[i];

                    for (int s = 0; s < num_cols; ++s)
                    {
                        VecPrec *halo_start = b.raw() + lda * s + m.manager->halo_offsets_before_glue[i];
                        VecPrec *received_halo = rank_start + s * halo_size;
                        cudaMemcpy(halo_start, received_halo, halo_size * sizeof(VecPrec), cudaMemcpyHostToDevice);
                    }

                    rank_start += num_cols * halo_size;
                }
            }
        }
        else
        {
            if (num_cols == 1)
            {
                offset = 0;

                // Copy into b, one neighbor at a time, one ring at a time
                for (int i = 0 ; i < neighbors ; i++)
                {
                    for (int j = 0; j < num_rings; j++)
                    {
                        int size = m.manager->halo_offsets_before_glue[j * neighbors + i + 1] * bsize - m.manager->halo_offsets_before_glue[j * neighbors + i] * bsize;

                        if (size != 0)
                        {
                            cudaMemcpy(b.raw() + m.manager->halo_offsets_before_glue[j * neighbors + i]*bsize, &(b.explicit_host_buffer[b.buffer_size + offset]), size * sizeof(typename TConfig::VecPrec), cudaMemcpyHostToDevice);
                        }

                        offset += size;
                    }
                }
            }
            else
            {
                FatalError("num_rings != 1 && num_cols != 1 not supported\n", AMGX_ERR_NOT_IMPLEMENTED);
            }
        }

#else
        FatalError("MPI Comms module requires compiling with MPI", AMGX_ERR_NOT_IMPLEMENTED);
#endif
    }
}
#endif
// if 0
#endif
//MPI
} // namespace amgx


#pragma once

#include <gsl/span>
#include <array>
#include "halley/core/api/audio_api.h"

namespace Halley
{
	class AudioSource {
	public:
		virtual ~AudioSource() {}

		virtual uint8_t getNumberOfChannels() const = 0;
		virtual size_t getSamplesLeft() const = 0;
		virtual bool isReady() const { return true; }
		virtual bool getAudioData(size_t numSamples, AudioMultiChannelSamples dst) = 0;
	};
}


// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once
#include <iosfwd>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <gsl/gsl>
#include "onnxruntime_config.h"

#ifndef DISABLE_ABSEIL
// Need to include abseil inlined_vector.h header directly here
// as hash tables cause CUDA 10.2 compilers to fail. inlined_vector.h is fine.
#ifdef _MSC_VER
#pragma warning(push)
// C4127: conditional expression is constant
#pragma warning(disable : 4127)
// C4324: structure was padded due to alignment specifier
// Usage of alignas causes some internal padding in places.
#pragma warning(disable : 4324)
#endif

#include <absl/container/inlined_vector.h>

#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif  // DISABLE_ABSEIL

namespace onnxruntime {
#ifdef __GNUC__
#pragma GCC diagnostic push
#ifdef HAS_NULL_DEREFERENCE
#pragma GCC diagnostic ignored "-Wnull-dereference"
#endif
#endif

constexpr size_t kTensorShapeSmallBufferElementsSize = 5;

#ifndef DISABLE_ABSEIL
// Use this type to build a shape and then create TensorShape.
using TensorShapeVector = absl::InlinedVector<int64_t, kTensorShapeSmallBufferElementsSize>;
#else
class TensorShapeVector : public std::vector<int64_t> {
  using Base = std::vector<int64_t>;

 public:
   using Base::Base;
};

#endif  // DISABLE_ABSEIL

inline TensorShapeVector ToShapeVector(const gsl::span<const int64_t>& span) {
  TensorShapeVector out;
  out.reserve(span.size());
  out.assign(span.begin(), span.end());
  return out;
}

inline gsl::span<const int64_t> ToConstSpan(const TensorShapeVector& vec) {
  return gsl::make_span(vec);
}

class TensorShape {
  // We use negative numbers for unknown symbolic dimension. Each negative
  // number represents a unique symbolic dimension.
 public:
  TensorShape() = default;

  TensorShape(const TensorShape& other) : TensorShape(other.GetDims()) {}
  TensorShape& operator=(const TensorShape& other);
  TensorShape& operator=(const gsl::span<const int64_t>& dims) {
    *this = TensorShape(dims);
    return *this;
  }

  TensorShape(TensorShape&& other) noexcept { operator=(std::move(other)); }
  TensorShape& operator=(TensorShape&& other) noexcept;

  TensorShape(gsl::span<const int64_t> dims);
  TensorShape(const TensorShapeVector& dims) : TensorShape(gsl::make_span(dims)) {}
  TensorShape(std::initializer_list<int64_t> dims) : TensorShape(gsl::make_span(dims.begin(), dims.end())) {}
  TensorShape(const int64_t* dimension_sizes, size_t dimension_count) : TensorShape(gsl::span<const int64_t>(dimension_sizes, dimension_count)) {}
  TensorShape(const std::vector<int64_t>& dims, size_t start, size_t end) : TensorShape(gsl::span<const int64_t>(&dims[start], end - start)) {}

  // Create a TensorShape that points to an existing buffer internally. As no copy is made, 'data' must remain valid for the life of the TensorShape
  static const TensorShape FromExistingBuffer(const std::vector<int64_t>& data) {
    return TensorShape(External{}, gsl::span<int64_t>(const_cast<int64_t*>(data.data()), data.size()));
  }

  /**
     Return the dimension specified by <idx>.
  */
  int64_t operator[](size_t idx) const { return values_[idx]; }
  int64_t& operator[](size_t idx) { return values_[idx]; }

  bool operator==(const TensorShape& other) const noexcept { return GetDims() == other.GetDims(); }
  bool operator!=(const TensorShape& other) const noexcept { return !(*this == other); }

  size_t NumDimensions() const noexcept {
    return values_.size();
  }

  /**
     Copy dims into an array with given size
  */
  void CopyDims(int64_t* dims, size_t num_dims) const {
    memcpy(dims, values_.data(), sizeof(int64_t) * std::min(num_dims, NumDimensions()));
  }

  /**
     Copy dims from a specific start dim into an array with given size
     `start_dim` is expected to be in the inclusive range [0, NumDimensions() - 1]
     and this function does no checks to ensure that
  */
  void CopyDims(int64_t* dims, size_t start_dim, size_t num_dims) const {
    memcpy(dims, values_.data() + start_dim, sizeof(int64_t) * std::min(num_dims, NumDimensions() - start_dim));
  }

  /**
     Return underlying vector representation.
  */
  gsl::span<const int64_t> GetDims() const { return values_; }

  TensorShapeVector AsShapeVector() const {
    return ToShapeVector(values_);
  }

  /**
   * Return the total number of elements. Returns 1 for an empty (rank 0) TensorShape.
   *
   * May return -1
   */
  int64_t Size() const;

  /**
     Return the total number of elements up to the specified dimension.
     If the dimension interval is empty (dimension == 0), return 1.
     @param dimension Return size up to this dimension. Value must be between 0 and this->NumDimensions(), inclusive.
  */
  int64_t SizeToDimension(size_t dimension) const;

  /**
     Return the total number of elements from the specified dimension to the end of the tensor shape.
     If the dimension interval is empty (dimension == this->NumDimensions()), return 1.
     @param dimension Return size from this dimension to the end. Value must be between 0 and this->NumDimensions(),
                      inclusive.
  */
  int64_t SizeFromDimension(size_t dimension) const;

  /**
     Return a new TensorShape of the dimensions from dimstart to dimend.
  */
  TensorShape Slice(size_t dimstart, size_t dimend) const;

  /**
     Return a new TensorShape of the dimensions from dimstart to end.
  */
  TensorShape Slice(size_t dimstart) const { return Slice(dimstart, values_.size()); }

  /**
     output dimensions nicely formatted
  */
  std::string ToString() const;

  /**
     Calculate size between start and end.
     Assumes start and end are between 0 and this->NumDimensions(), inclusive, and that
     start < end.
  */
  int64_t SizeHelper(size_t start, size_t end) const;

  /**
     empty shape or 1D shape (1) is regarded as scalar tensor
  */
  bool IsScalar() const {
    size_t len = values_.size();
    return len == 0 || (len == 1 && values_[0] == 1);
  }

 private:
  struct External {};
  TensorShape(External, gsl::span<int64_t> buffer) : values_{buffer} {}

  void Allocate(size_t size);

  gsl::span<int64_t> values_;
  int64_t small_buffer_[kTensorShapeSmallBufferElementsSize];
  std::unique_ptr<int64_t[]> allocated_buffer_;

  friend struct ProviderHostImpl;  // So that the shared provider interface can access Allocate
};
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
// operator<< to nicely output to a stream
std::ostream& operator<<(std::ostream& out, const TensorShape& shape);

}  // namespace onnxruntime