/* @copyright Russell Standish 2000-2013 @author Russell Standish This file is part of Graphcode Open source licensed under the MIT license. See LICENSE for details. */ #ifndef GRAPHCODE_H #define GRAPHCODE_H #include #include #include #include #include #include #ifdef MPI_SUPPORT #include #ifdef PARMETIS #include #else typedef int idx_t; /* just for defining dummy weight functions */ #endif #else typedef int idx_t; /* just for defining dummy weight functions */ #endif /* MPI_SUPPORT */ #include #include #include #include #include #include #include #include #ifdef SYCL_LANGUAGE_VERSION #include #endif namespace classdesc { struct RESTProcess_t; } namespace graphcode { /// Type used for Graph object identifier typedef unsigned long GraphId; /** a pin with ID==badId cannot be inserted into a map or wire - can be used for handling boundary conditions during graph construction */ const GraphId badId=~0UL; using std::vector; using std::map; using std::set; using std::find_if; using std::find; using std::cout; using std::endl; using namespace classdesc; using classdesc::string; #ifdef MPI_SUPPORT /* MPI_Finalized only available in MPI-2 standard */ inline bool MPI_running() { int fi, ff=0; MPI_Initialized(&fi); #if (defined(MPI_VERSION) && MPI_VERSION>1 || defined(MPICH_NAME)) MPI_Finalized(&ff); #endif /* MPI_VERSION>1 */ return fi&&!ff; } #endif /* MPI_SUPPORT */ /// return my processor no. inline unsigned myid() { int m=0; #ifdef MPI_SUPPORT if (MPI_running()) MPI_Comm_rank(MPI_COMM_WORLD,&m); #endif return m; } /// return number of processors inline unsigned nprocs() { int m=1; #ifdef MPI_SUPPORT if (MPI_running()) MPI_Comm_size(MPI_COMM_WORLD,&m); #endif return m; } /** Utility for for returning \a val % \a limit - \a val \f$\in[-l,2l)\f$ where \f$l\f$=\a limit */ template inline TYPE Wrap(TYPE val, TYPE limit) { if( val >= limit ) return val-limit; else if( val < 0 ) return val+limit; else return val; } class object; class ObjectPtrBase: public std::shared_ptr { GraphId m_id; public: GraphId id() const {return m_id;} unsigned proc=0; ObjectPtrBase(GraphId id=badId, const std::shared_ptr& x=nullptr): std::shared_ptr(x), m_id(id) {} ObjectPtrBase(GraphId id, std::shared_ptr&& x): std::shared_ptr(x), m_id(id) {} ObjectPtrBase& operator=(const ObjectPtrBase& x) { // specialisation to ensure m_id is not overwritten std::shared_ptr::operator=(x); proc=x.proc; return *this; } }; template class ObjectPtr: public ObjectPtrBase { public: ObjectPtr(GraphId id=badId, const std::shared_ptr& x=nullptr): ObjectPtrBase(id, x) {} ObjectPtr(GraphId id, std::shared_ptr&& x): ObjectPtrBase(id, x) {} T& operator*() const {return static_cast(ObjectPtrBase::operator*());} T* operator->() const {return static_cast(ObjectPtrBase::operator->());} }; /* base object of graphcode - can be a pin or a wire - whatever */ /** Reference to an object type */ class ObjRef { public: ObjectPtrBase *payload=nullptr; /* referenced data */ CLASSDESC_ACCESS(ObjRef); public: ObjRef()=default; ObjRef(const ObjectPtrBase& x): payload(const_cast(&x)) {} GraphId id() const {return payload? payload->id(): badId;} unsigned proc() const {return payload? payload->proc: 0;} unsigned proc(unsigned p) {if (payload) payload->proc=p; return proc();} object& operator*() const {return **payload;} object* operator->() const {return payload->get();} operator bool() const {return payload && *payload;} operator ObjectPtrBase() const {return *payload;} void nullify() {payload->reset();} }; /// Allocator class - handles SYCL USM allocation, delegates to std::allocator when not needed #ifdef SYCL_LANGUAGE_VERSION template class Allocator { sycl::context context; sycl::device device; sycl::usm::alloc type=sycl::usm::alloc::unknown; template friend class Allocator; public: using value_type=T; using pointer=T*; using reference=T&; using difference_type=std::ptrdiff_t; using propagate_on_container_move_assignment=std::true_type; Allocator()=default; Allocator(const sycl::context& context, const sycl::device& device, sycl::usm::alloc type): context(context), device(device), type(type) {} Allocator(const sycl::queue& queue, sycl::usm::alloc type): Allocator(queue.get_context(),queue.get_device(),type) {} template Allocator(const Allocator& x): context(x.context), device(x.device), type(x.type) {} template struct rebind {using other=Allocator;}; T* allocate(size_t n) { if (type==sycl::usm::alloc::unknown) return std::allocator().allocate(n); else return sycl::malloc(n,device,context,type); } void deallocate(T* p,size_t) { if (type==sycl::usm::alloc::unknown) return std::free(p); else return sycl::free(p,context); } bool operator==(const Allocator& x) const { return type==x.type && context==x.context && device==x.device; } }; #else template class Allocator: public std::allocator { public: Allocator()=default; template constexpr Allocator(const Allocator& x) noexcept: std::allocator(x) {} template struct rebind {using other=Allocator;}; }; #endif /** Vector of references to objects: - serialisable - objects are not owned by this class */ // PtrList needs copy operations clobbered. struct PtrList: std::vector> { using Allocator=graphcode::Allocator; PtrList()=default; PtrList(const PtrList& x) {} template PtrList(I begin, I end, const Allocator& alloc={}): std::vector(begin,end,alloc) {} PtrList& operator=(const PtrList&) {return *this;} // set a new allocation scheme for this vector. This copies any existing elements into the new space void setAllocator(const PtrList::Allocator& alloc) { if (get_allocator()==alloc) return; // nothing to do optimisation PtrList tmp(begin(),end(),alloc); // moving the vector moves the allocator as well, because propagate_on_container_move_assignment is true std::vector::operator=(std::move(tmp)); } }; /** base class for Graphcode objects. an object, first and foremost is a \c Ptrlist of other objects it is connected to (maybe its neighbours, maybe its classes or families to which it belongs) */ class object: public Exclude, public classdesc::object, public classdesc::PolyRESTProcessBase { public: std::vector neighbours; /// construct the internal pointer-based neighbour list, given the list of neighbours in \a neighbours template void updatePtrList(const OMap& o, const Allocator& alloc={}) { clear(); setAllocator(alloc); for (auto& n: neighbours) { auto i=o.find(n); if (i!=o.end()) { assert(*i); emplace_back(*i); } } } /// clone an object of a particular type. Note it is incorrect to /// call this method on an object that is not T. Runtime checks /// are not performed. template T* cloneObject() const { #ifndef SYCL_LANGUAGE_VERSION assert(dynamic_cast(this)); #endif return static_cast(clone()); } /// return a reference to this template T* as() { #ifndef SYCL_LANGUAGE_VERSION assert(dynamic_cast(this)); #endif return static_cast(this); } template const T* as() const { #ifndef SYCL_LANGUAGE_VERSION assert(dynamic_cast(this)); #endif return static_cast(this); } virtual idx_t weight() const {return 1;} ///< node's weight (for partitioning) /// weight for edge connecting \c *this to \a x virtual idx_t edgeWeight(const ObjRef& x) const {return 1;} }; /// Curiously recursive template pattern to define classdesc'd methods template struct Object: public classdesc::Object { void RESTProcess(RESTProcess_t& r,const classdesc::string& d) override { classdesc::RESTProcess(r,d,static_cast(*this)); } void RESTProcess(RESTProcess_t& r,const classdesc::string& d) const override { classdesc::RESTProcess(r,d,static_cast(*this)); } }; template inline bool operator<(const ObjectPtr& x, const ObjectPtr& y) {return x.id() struct Hash { size_t operator()(const ObjectPtr& x) const {return std::hash()(x.id());} }; template struct KeyEqual { bool operator()(const ObjectPtr& x, const ObjectPtr& y) const { return x.id()==y.id();} }; template using OMapImpl=std::unordered_set, Hash, KeyEqual, Allocator>>; template struct OMap: public OMapImpl { using Super=std::unordered_set, Hash, KeyEqual, Allocator>>; using Super::erase; using Super::count; using Super::insert; OMap(const Allocator>& allocator={}): OMapImpl(allocator) {} typename OMap::Super::iterator find(GraphId id) { ObjectPtr tmp(id); return Super::find(tmp); } typename OMap::Super::const_iterator find(GraphId id) const { ObjectPtr tmp(id); return Super::find(tmp); } size_t erase(GraphId id) { ObjectPtr tmp(id); return Super::erase(tmp); } size_t count(GraphId id) const { ObjectPtr tmp(id); return OMapImpl::count(tmp); } ObjectPtr& operator[](GraphId id) { // const_cast OK because id() is immutable return const_cast&>(*OMapImpl::emplace(id).first); } const ObjectPtr& operator[](GraphId id) const { auto& i=find(id); if (i==this->end()) return badId; return *i; } OMap deepCopy() { OMap r; for (auto& x: *this) r.insert(ObjectPtr (x.id(), std::shared_ptr(x? x->template cloneObject(): nullptr))); return r; } bool noNulls() const { bool r=true; for (auto& i: *this) r &= bool(i); return r; } // true if all keys are distinct bool sane() const { set keys; for (auto& i: *this) if (!keys.insert(i.id()).second) return false; return true; } }; class GraphBase: public PtrList { protected: vector > rec_req; vector > requests; unsigned tag=0; /* tag used to ensure message groups do not overlap */ /// checks that objects all have unique keys (ids). virtual bool sane() const=0; CLASSDESC_ACCESS(GraphBase); public: static bool typeRegistered(const graphcode::object& x) {return x.type()>=0;} PtrList objectRefs; virtual ObjectPtrBase& objectRef(GraphId)=0; virtual ~GraphBase() {} /** Rebuild the list of locally hosted objects */ virtual void rebuildPtrLists()=0; /** remove from local memory any objects not hosted locally */ void purge() { std::unordered_set references; for (auto& i: objectRefs) if (i.proc()==myid()) { references.insert(i.id()); for (auto id: i->neighbours) references.insert(id); } // now remove all unreferenced items for (auto& i: this->objectRefs) if (references.count(i.id())==0) i.nullify(); } /** print IDs of objects hosted on proc 0, for debugging purposes */ void print(unsigned proc) { if (proc==myid()) for (auto& i: *this) { std::cout << " i->ID="< class Graph: public GraphBase { ObjectPtrBase& objectRef(GraphId id) override {return objects[id];} bool sane() const override {return objects.sane();} CLASSDESC_ACCESS(Graph); graphcode::Allocator cellAlloc; PtrList::Allocator ptrListAlloc; public: using Cell=T; using OMapAllocator=graphcode::Allocator>; OMap objects; Graph(const graphcode::Allocator& cellAlloc={}, const PtrList::Allocator& ptrListAlloc={}, const typename Graph::OMapAllocator& mapAllocator={}): cellAlloc(cellAlloc), ptrListAlloc(ptrListAlloc), objects(mapAllocator) {} void rebuildPtrLists() override { clear(); setAllocator(ptrListAlloc); objectRefs.clear(); for (auto& i: objects) { objectRefs.emplace_back(i); if (i.proc==myid()) { assert(i); emplace_back(i); } if (i) i->updatePtrList(objects,ptrListAlloc); } } /** distribute objects from proc 0 according to partitioning set in the \c objref's \c proc field */ void distributeObjects() { #ifdef MPI_SUPPORT rec_req.clear(); MPIbuf() << objects << bcast(0) >> objects; #endif rebuildPtrLists(); } /** add the specified object into the Graph, if not already present */ ObjRef insertObject(const ObjectPtr& o) { auto& i=*(objects.emplace(o).first); if (i) { i->type(); /* ensure type is registered */ assert(typeRegistered(*i)); } return i; } /** add the specified object into the Graph, replacing any already present */ ObjRef overwriteObject(const ObjectPtr& o) { auto i=objects.find(o.id()); if (i==objects.end()) return insertObject(o); // const cast OK here because id is not changed const_cast&>(*i)=o; return *i; } /** add an object of type U if none already present: - use as graph.AddObject(id, args...); where args... are any arguments required by foo's constructor - does not create new object if one is already present */ template ObjRef insertObject(GraphId id, Args... args) { auto i=objects.find(id); if (i==objects.end()) return insertObject(ObjectPtr(id, std::allocate_shared(cellAlloc,std::forward(args)...))); return *i; } }; } #ifdef _CLASSDESC #pragma omit pack graphcode::ObjectPtrBase #pragma omit unpack graphcode::ObjectPtrBase #pragma omit RESTProcess graphcode::ObjectPtrBase #pragma omit pack graphcode::OMap #pragma omit unpack graphcode::OMap #endif namespace classdesc_access { namespace cd=classdesc; template <> struct access_pack { template void operator()(cd::pack_t& p, const cd::string& d, U& a) { p<&>(a); } }; template <> struct access_unpack { void operator()(cd::unpack_t& p, const cd::string& d, graphcode::ObjectPtrBase& a) { p>>a.proc>>static_cast&>(a); } void operator()(cd::unpack_t& p, const cd::string& d, const graphcode::ObjectPtrBase& a) { graphcode::ObjectPtrBase tmp; (*this)(p,d,tmp); } }; template <> struct access_RESTProcess { template void operator()(cd::RESTProcess_t& r, const cd::string& d, U& a) { ::RESTProcess(r,d+".id",a,&::graphcode::ObjectPtrBase::id); ::RESTProcess(r,d+".proc",a,&::graphcode::ObjectPtrBase::proc); if (a) a->RESTProcess(r,d); } }; template struct access_pack> { template void operator()(cd::pack_t& p, const cd::string& d, U& a) { for (auto& i: a) p< struct access_unpack> { void operator()(cd::unpack_t& p, const cd::string& d, graphcode::OMap& a) { while (p) { graphcode::GraphId id; p>>id; p>>a[id]; } } void operator()(cd::unpack_t& p, const cd::string& d, const graphcode::OMap&) { graphcode::OMap tmp; (*this)(p,d,tmp); } }; } #define CLASSDESC_RESTProcess___graphcode__Allocator_T_ #define CLASSDESC_json_pack___graphcode__Allocator_T_ #define CLASSDESC_json_unpack___graphcode__Allocator_T_ #define CLASSDESC_pack___graphcode__Allocator_T_ #define CLASSDESC_unpack___graphcode__Allocator_T_ namespace classdesc_access { template struct access_pack>: public classdesc::NullDescriptor {}; template struct access_unpack>: public classdesc::NullDescriptor {}; template struct access_json_pack>: public classdesc::NullDescriptor {}; template struct access_json_unpack>: public classdesc::NullDescriptor {}; } #endif /* GRAPHCODE_H */