diff --git a/Makefile b/Makefile
index 47a07f46cb23371ccecbe5ef49379adbb5842565..ba82842577390602a55904c2cb8c71a8863909cc 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ lj_ns.cu:
 # Targets
 lj_cpu: lj_ns.cpp
 #	g++ -o lj_cpu lj_ns.cpp -DDEBUG
-	mpic++ -O3 -o lj_cpu lj_ns.cpp -DDEBUG
+	mpic++ -O3 -o lj_cpu lj_ns.cpp runtime/pairs.cpp runtime/devices/dummy.cpp -DDEBUG
 
 lj_gpu: lj_ns.cu
 	nvcc -o lj_gpu lj_ns.cu
diff --git a/runtime/array.hpp b/runtime/array.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a755c4b07a70e4837ec02cbf1786071a53e3ea1
--- /dev/null
+++ b/runtime/array.hpp
@@ -0,0 +1,37 @@
+#include "pairs_common.hpp"
+
+#pragma once
+
+namespace pairs {
+
+class Array {
+protected:
+    array_t id;
+    std::string name;
+    void *h_ptr, *d_ptr;
+    size_t size;
+    bool is_static;
+
+public:
+    Array(array_t id_, std::string name_, void *h_ptr_, void *d_ptr_, size_t size_, bool is_static_ = false) :
+        id(id_),
+        name(name_),
+        h_ptr(h_ptr_),
+        d_ptr(d_ptr_),
+        size(size_),
+        is_static(is_static_) {
+
+        PAIRS_ASSERT(size_ > 0);
+    }
+
+    property_t getId() { return id; }
+    std::string getName() { return name; }
+    void *getHostPointer() { return h_ptr; }
+    void *getDevicePointer() { return d_ptr; }
+    void setPointers(void *h_ptr_, void *d_ptr_) { h_ptr = h_ptr_, d_ptr = d_ptr_; }
+    void setSize(size_t size_) { size = size_; }
+    size_t getSize() { return size; };
+    bool isStatic() { return is_static; }
+};
+
+}
diff --git a/runtime/device_flags.hpp b/runtime/device_flags.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4b5085fb6b61f8df1c3e1e129541266765745fdf
--- /dev/null
+++ b/runtime/device_flags.hpp
@@ -0,0 +1,95 @@
+#include <cmath>
+#include <iostream>
+
+#pragma once
+
+namespace pairs {
+
+class DeviceFlags {
+private:
+    unsigned long long int *hflags;
+    unsigned long long int *dflags;
+    int narrays;
+    int nflags;
+    static const int narrays_per_flag = 64;
+public:
+    DeviceFlags(int narrays_) : narrays(narrays_) {
+        nflags = std::ceil((double) narrays_ / (double) narrays_per_flag);
+        hflags = new unsigned long long int[nflags];
+        dflags = new unsigned long long int[nflags];
+
+        for(int i = 0; i < nflags; ++i) {
+            hflags[i] = 0xfffffffffffffffful;
+            dflags[i] = 0x0;
+        }
+    }
+
+    inline bool isHostFlagSet(int array_id) {
+        int flag_index = array_id / narrays_per_flag;
+        unsigned long long int bit = array_id % narrays_per_flag;
+        return (hflags[flag_index] & (1 << bit)) != 0;
+    }
+
+    inline void setHostFlag(int array_id) {
+        int flag_index = array_id / narrays_per_flag;
+        unsigned long long int bit = array_id % narrays_per_flag;
+        hflags[flag_index] |= 1 << bit;
+    }
+
+    inline void clearHostFlag(int array_id) {
+        int flag_index = array_id / narrays_per_flag;
+        unsigned long long int bit = array_id % narrays_per_flag;
+        hflags[flag_index] &= ~(1 << bit);
+    }
+
+    inline bool isDeviceFlagSet(int array_id) {
+        int flag_index = array_id / narrays_per_flag;
+        unsigned long long int bit = array_id % narrays_per_flag;
+        return (dflags[flag_index] & (1 << bit)) != 0;
+    }
+
+    inline void setDeviceFlag(int array_id) {
+        int flag_index = array_id / narrays_per_flag;
+        unsigned long long int bit = array_id % narrays_per_flag;
+        dflags[flag_index] |= 1 << bit;
+    }
+
+    inline void clearDeviceFlag(int array_id) {
+        int flag_index = array_id / narrays_per_flag;
+        unsigned long long int bit = array_id % narrays_per_flag;
+        dflags[flag_index] &= ~(1 << bit);
+    }
+
+    void printFlags() {
+        std::cerr << "hflags = ";
+        for(int i = 0; i < nflags; i++) {
+            for(int b = 63; b >= 0; b--) {
+                if(hflags[i] & (1 << b)) {
+                    std::cerr << "1";
+                } else {
+                    std::cerr << "0";
+                }
+            }
+        }
+
+        std::cerr << std::endl << "dflags = ";
+        for(int i = 0; i < nflags; i++) {
+            for(int b = 63; b >= 0; b--) {
+                if(dflags[i] & (1 << b)) {
+                    std::cerr << "1";
+                } else {
+                    std::cerr << "0";
+                }
+            }
+        }
+
+        std::cerr << std::endl;
+    }
+
+    ~DeviceFlags() {
+        delete[] hflags;
+        delete[] dflags;
+    }
+};
+
+}
diff --git a/runtime/devices/cuda.hpp b/runtime/devices/cuda.cu
similarity index 88%
rename from runtime/devices/cuda.hpp
rename to runtime/devices/cuda.cu
index 60c5ceba8b5a08d1c344fd9b232466d17861e39a..50b35006f2bbf70be3879abd539eaa882e66f4c2 100644
--- a/runtime/devices/cuda.hpp
+++ b/runtime/devices/cuda.cu
@@ -43,8 +43,8 @@ __host__ void copy_static_symbol_to_host(void *d_ptr, const void *h_ptr, size_t
     //CUDA_ASSERT(cudaMemcpyFromSymbol(h_ptr, d_ptr, count));
 }
 
-inline __device__ int atomic_add(int *addr, int val) { return atomicAdd(addr, val); }
-inline __device__ int atomic_add_resize_check(int *addr, int val, int *resize, int capacity) {
+__device__ int atomic_add(int *addr, int val) { return atomicAdd(addr, val); }
+__device__ int atomic_add_resize_check(int *addr, int val, int *resize, int capacity) {
     const int add_res = *addr + val;
     if(add_res >= capacity) {
         *resize = add_res;
diff --git a/runtime/devices/device.hpp b/runtime/devices/device.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..30b1d1e44e60b4bbfa30171f5d50fc510eae5891
--- /dev/null
+++ b/runtime/devices/device.hpp
@@ -0,0 +1,24 @@
+#include <iostream>
+#include <cstddef>
+
+#pragma once
+
+#ifndef PAIRS_TARGET_CUDA
+#   define __host__
+#   define __device__
+typedef int cudaError_t;
+#endif
+
+namespace pairs {
+
+void cuda_assert(cudaError_t err, const char *file, int line);
+__host__ void *device_alloc(size_t size);
+__host__ void *device_realloc(void *ptr, size_t size);
+__host__ void copy_to_device(const void *h_ptr, void *d_ptr, size_t count);
+__host__ void copy_to_host(const void *d_ptr, void *h_ptr, size_t count);
+__host__ void copy_static_symbol_to_device(void *h_ptr, const void *d_ptr, size_t count);
+__host__ void copy_static_symbol_to_host(void *d_ptr, const void *h_ptr, size_t count);
+__device__ int atomic_add(int *addr, int val);
+__device__ int atomic_add_resize_check(int *addr, int val, int *resize, int capacity);
+
+}
diff --git a/runtime/devices/dummy.hpp b/runtime/devices/dummy.cpp
similarity index 65%
rename from runtime/devices/dummy.hpp
rename to runtime/devices/dummy.cpp
index 3daebcf5804046b21c12e700e88f27b34dabd13a..8b8c80a85027c2c4a861dfce9e0cb57d66175b65 100644
--- a/runtime/devices/dummy.hpp
+++ b/runtime/devices/dummy.cpp
@@ -1,11 +1,11 @@
-#pragma once
+#include "device.hpp"
 
 namespace pairs {
 
-void *device_alloc(size_t size) { return NULL; }
-void *device_realloc(void *ptr, size_t size) { return NULL; }
-void copy_to_device(void *h_ptr, const void *d_ptr, size_t count) {}
-void copy_to_host(void *d_ptr, const void *h_ptr, size_t count) {}
+void *device_alloc(size_t size) { return nullptr; }
+void *device_realloc(void *ptr, size_t size) { return nullptr; }
+void copy_to_device(void const *h_ptr, void *d_ptr, size_t count) {}
+void copy_to_host(void const *d_ptr, void *h_ptr, size_t count) {}
 void copy_static_symbol_to_device(void *h_ptr, const void *d_ptr, size_t count) {}
 void copy_static_symbol_to_host(void *d_ptr, const void *h_ptr, size_t count) {}
 int atomic_add(int *addr, int val) {
diff --git a/runtime/domain/domain_partitioning.hpp b/runtime/domain/domain_partitioning.hpp
index 5e628f867cd3de109ca70397e44fbbbb6e1c7a69..c4a231b3e14c5ab6ea911f1b830a348399f55677 100644
--- a/runtime/domain/domain_partitioning.hpp
+++ b/runtime/domain/domain_partitioning.hpp
@@ -1,37 +1,45 @@
 #include <mpi.h>
+//---
+#include "../pairs_common.hpp"
 
 #pragma once
 
-typedef double real_t;
-
 namespace pairs {
 
-template<int ndims> class Regular6DStencil;
+class Regular6DStencil;
 
-template<int ndims> class DomainPartitioner {
-    friend class Regular6DStencil<ndims>;
+class DomainPartitioner {
+    friend class Regular6DStencil;
 
 protected:
-    real_t grid_min[ndims];
-    real_t grid_max[ndims];
-    
+    real_t *grid_min;
+    real_t *grid_max;
+    int ndims;
+
 public:
     DomainPartitioner(real_t xmin, real_t xmax, real_t ymin, real_t ymax, real_t zmin, real_t zmax) {
-        static_assert(ndims == 3, "DomainPartitioner(): number of dimensions mismatching!");
-        //PAIRS_ASSERT(xmax > xmin);
-        //PAIRS_ASSERT(ymax > ymin);
-        //PAIRS_ASSERT(zmax > zmin);
+        PAIRS_ASSERT(xmax > xmin);
+        PAIRS_ASSERT(ymax > ymin);
+        PAIRS_ASSERT(zmax > zmin);
 
+        ndims = 3;
+        grid_min = new real_t[ndims];
+        grid_max = new real_t[ndims];
         grid_min[0] = xmin;
-        grid_max[0] = xmax;
         grid_min[1] = ymin;
-        grid_max[1] = ymax;
         grid_min[2] = zmin;
+        grid_max[0] = xmax;
+        grid_max[1] = ymax;
         grid_max[2] = zmax;
     }
 
+    ~DomainPartitioner() {
+        delete[] grid_min;
+        delete[] grid_max;
+    }
+
     virtual void initialize(int *argc, char ***argv) = 0;
-    virtual void fillArrays(int neighbor_ranks[], int pbc[], real_t subdom[]) = 0;
+    virtual void fillArrays(int *neighbor_ranks, int *pbc, real_t *subdom) = 0;
     virtual void communicateSizes(int dim, const int *nsend, int *nrecv) = 0;
     virtual void communicateData(
         int dim, int elem_size,
@@ -40,6 +48,4 @@ public:
     virtual void finalize() = 0;
 };
 
-//class DomainPartitioner<3>;
-
 }
diff --git a/runtime/domain/no_partition.hpp b/runtime/domain/no_partition.hpp
index 51eaa2f3737424a91932216d41c8c213f1db0e0c..b9ac35748f5a4c9387c0853f722c141f687ecadc 100644
--- a/runtime/domain/no_partition.hpp
+++ b/runtime/domain/no_partition.hpp
@@ -6,8 +6,7 @@
 
 namespace pairs {
 
-template <int ndims>
-class NoPartitioning : DimensionRanges<ndims> {
+class NoPartitioning : DimensionRanges {
 public:
     void initialize(int *argc, const char **argv) {
         for(int d = 0; d < ndims; d++) {
diff --git a/runtime/domain/regular_6d_stencil.hpp b/runtime/domain/regular_6d_stencil.hpp
index 4542f2fe80fc32469fd171c23456524be8ac2a98..631ded149ca38869db7f9c797dbd61cc6aa80075 100644
--- a/runtime/domain/regular_6d_stencil.hpp
+++ b/runtime/domain/regular_6d_stencil.hpp
@@ -1,32 +1,49 @@
+#include "../pairs_common.hpp"
 #include "domain_partitioning.hpp"
 
 #pragma once
 
 #define SMALL   0.00001
 
-typedef double real_t;
-
 namespace pairs {
 
-template <int ndims>
-class Regular6DStencil : public DomainPartitioner<ndims> {
+class Regular6DStencil : public DomainPartitioner {
 private:
     int world_size, rank;
-    int nranks[ndims];
-    int prev[ndims];
-    int next[ndims];
-    int pbc_prev[ndims];
-    int pbc_next[ndims];
-    real_t subdom_min[ndims];
-    real_t subdom_max[ndims];
+    int *nranks;
+    int *prev;
+    int *next;
+    int *pbc_prev;
+    int *pbc_next;
+    real_t *subdom_min;
+    real_t *subdom_max;
 
 public:
     Regular6DStencil(real_t xmin, real_t xmax, real_t ymin, real_t ymax, real_t zmin, real_t zmax) :
-        DomainPartitioner<ndims>(xmin, xmax, ymin, ymax, zmin, zmax) {}
+        DomainPartitioner(xmin, xmax, ymin, ymax, zmin, zmax) {
+
+        nranks = new int[ndims];
+        prev = new int[ndims];
+        next = new int[ndims];
+        pbc_prev = new int[ndims];
+        pbc_next = new int[ndims];
+        subdom_min = new real_t[ndims];
+        subdom_max = new real_t[ndims];
+    }
+
+    ~Regular6DStencil() {
+        delete[] nranks;
+        delete[] prev;
+        delete[] next;
+        delete[] pbc_prev;
+        delete[] pbc_next;
+        delete[] subdom_min;
+        delete[] subdom_max;
+    }
 
     void setConfig() {
-        static_assert(ndims == 3, "setConfig() only implemented for three dimensions!");
-        real_t area[ndims];
+        PAIRS_ASSERT(ndims == 3);
+        real_t area[3];
         real_t best_surf = 0.0;
         int d = 0;
 
@@ -60,9 +77,9 @@ public:
 
     void setBoundingBox() {
         MPI_Comm cartesian;
-        int myloc[ndims];
-        int periods[ndims];
-        real_t rank_length[ndims];
+        int *myloc = new int[ndims];
+        int *periods = new int[ndims];
+        real_t *rank_length = new real_t[ndims];
         int reorder = 0;
 
         for(int d = 0; d < ndims; d++) {
@@ -80,6 +97,9 @@ public:
             subdom_max[d] = subdom_min[d] + rank_length[d];
         }
 
+        delete[] myloc;
+        delete[] periods;
+        delete[] rank_length;
         MPI_Comm_free(&cartesian);
     }
 
@@ -103,7 +123,7 @@ public:
                z >= subdom_min[2] && z < subdom_max[2] - SMALL;
     }
 
-    void fillArrays(int neighbor_ranks[], int pbc[], real_t subdom[]) {
+    void fillArrays(int *neighbor_ranks, int *pbc, real_t *subdom) {
         for(int d = 0; d < ndims; d++) {
             neighbor_ranks[d * 2 + 0] = prev[d];
             neighbor_ranks[d * 2 + 1] = next[d];
diff --git a/runtime/pairs.cpp b/runtime/pairs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2fcb2d60cadeffe7f722a75e7a86474ef9a759a9
--- /dev/null
+++ b/runtime/pairs.cpp
@@ -0,0 +1,172 @@
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+#include <memory>
+#include <vector>
+//---
+#include "pairs.hpp"
+#include "pairs_common.hpp"
+#include "devices/device.hpp"
+#include "domain/regular_6d_stencil.hpp"
+
+namespace pairs {
+
+void PairsSimulation::initDomain(int *argc, char ***argv, real_t xmin, real_t xmax, real_t ymin, real_t ymax, real_t zmin, real_t zmax) {
+    if(dom_part_type == DimRanges) {
+        dom_part = new Regular6DStencil(xmin, xmax, ymin, ymax, zmin, zmax);
+    } else {
+        PAIRS_EXCEPTION("Domain partitioning type not implemented!\n");
+    }
+
+    dom_part->initialize(argc, argv);
+}
+
+void PairsSimulation::addArray(Array array) {
+    int id = array.getId();
+    auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
+    PAIRS_ASSERT(a == std::end(arrays));
+    arrays.push_back(array);
+}
+
+Array &PairsSimulation::getArray(array_t id) {
+    auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
+    PAIRS_ASSERT(a != std::end(arrays));
+    return *a;
+}
+
+Array &PairsSimulation::getArrayByName(std::string name) {
+    auto a = std::find_if(arrays.begin(), arrays.end(), [name](Array a) { return a.getName() == name; });
+    PAIRS_ASSERT(a != std::end(arrays));
+    return *a;
+}
+
+void PairsSimulation::addProperty(Property prop) {
+    int id = prop.getId();
+    auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
+    PAIRS_ASSERT(p == std::end(properties));
+    properties.push_back(prop);
+}
+
+Property &PairsSimulation::getProperty(property_t id) {
+    auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
+    PAIRS_ASSERT(p != std::end(properties));
+    return *p;
+}
+
+Property &PairsSimulation::getPropertyByName(std::string name) {
+    auto p = std::find_if(properties.begin(), properties.end(), [name](Property p) { return p.getName() == name; });
+    PAIRS_ASSERT(p != std::end(properties));
+    return *p;
+}
+
+void PairsSimulation::copyArrayToDevice(Array &array) {
+    int array_id = array.getId();
+    if(!array_flags->isDeviceFlagSet(array_id)) {
+        if(array.isStatic()) {
+            PAIRS_DEBUG("Copying static array %s to device\n", array.getName().c_str());
+            pairs::copy_static_symbol_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
+        } else {
+            PAIRS_DEBUG("Copying array %s to device\n", array.getName().c_str());
+            pairs::copy_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
+        }
+    }
+}
+
+void PairsSimulation::copyArrayToHost(Array &array) {
+    int array_id = array.getId();
+    if(!array_flags->isHostFlagSet(array_id)) {
+        if(array.isStatic()) {
+            PAIRS_DEBUG("Copying static array %s to host\n", array.getName().c_str());
+            pairs::copy_static_symbol_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
+        } else {
+            PAIRS_DEBUG("Copying array %s to host\n", array.getName().c_str());
+            pairs::copy_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
+        }
+    }
+}
+
+void PairsSimulation::copyPropertyToDevice(Property &prop) {
+    if(!prop_flags->isDeviceFlagSet(prop.getId())) {
+        PAIRS_DEBUG("Copying property %s to device\n", prop.getName().c_str());
+        pairs::copy_to_device(prop.getHostPointer(), prop.getDevicePointer(), prop.getTotalSize());
+    }
+}
+
+void PairsSimulation::copyPropertyToHost(Property &prop) {
+    if(!prop_flags->isHostFlagSet(prop.getId())) {
+        PAIRS_DEBUG("Copying property %s to host\n", prop.getName().c_str());
+        pairs::copy_to_host(prop.getDevicePointer(), prop.getHostPointer(), prop.getTotalSize());
+    }
+}
+
+void PairsSimulation::communicateSizes(int dim, const int *send_sizes, int *recv_sizes) {
+    this->getDomainPartitioner()->communicateSizes(dim, send_sizes, recv_sizes);
+    PAIRS_DEBUG("send_sizes=[%d, %d], recv_sizes=[%d, %d]\n", send_sizes[dim * 2 + 0], send_sizes[dim * 2 + 1], recv_sizes[dim * 2 + 0], recv_sizes[dim * 2 + 1]);
+}
+
+void PairsSimulation::communicateData(
+    int dim, int elem_size,
+    const real_t *send_buf, const int *send_offsets, const int *nsend,
+    real_t *recv_buf, const int *recv_offsets, const int *nrecv) {
+
+    this->getDomainPartitioner()->communicateData(dim, elem_size, send_buf, send_offsets, nsend, recv_buf, recv_offsets, nrecv);
+
+    /*
+    // Debug messages
+    const int elems_to_print = 5;
+
+    // Send buffer debug
+    for(int i = 0; i < 2; i++) {
+        int nsnd = nsend[dim * 2 + i];
+
+        PAIRS_DEBUG("send_buf=[");
+        for(int j = 0; j < MIN(elems_to_print, nsnd); j++) {
+            for(int k = 0; k < elem_size; k++) {
+                PAIRS_DEBUG("%f,", send_buf[(send_offsets[dim * 2 + i] + j) * elem_size + k]);
+            }
+        }
+
+        if(elems_to_print * 2 < nsnd) {
+            PAIRS_DEBUG("\b ... ");
+        }
+
+        for(int j = MAX(elems_to_print, nsnd - elems_to_print); j < nsnd; j++) {
+            for(int k = 0; k < elem_size; k++) {
+                PAIRS_DEBUG("%f,", send_buf[(send_offsets[dim * 2 + i] + j) * elem_size + k]);
+            }
+        }
+
+        PAIRS_DEBUG("\b]\n");
+    }
+
+    // Receive buffer debug
+    for(int i = 0; i < 2; i++) {
+        int nrec = nrecv[dim * 2 + i];
+
+        PAIRS_DEBUG("recv_buf=[");
+        for(int j = 0; j < MIN(elems_to_print, nrec); j++) {
+            for(int k = 0; k < elem_size; k++) {
+                PAIRS_DEBUG("%f,", recv_buf[(recv_offsets[dim * 2 + i] + j) * elem_size + k]);
+            }
+        }
+
+        if(elems_to_print * 2 < nrec) {
+            PAIRS_DEBUG("\b ... ");
+        }
+
+        for(int j = MAX(elems_to_print, nrec - elems_to_print); j < nrec; j++) {
+            for(int k = 0; k < elem_size; k++) {
+                PAIRS_DEBUG("%f,", recv_buf[(recv_offsets[dim * 2 + i] + j) * elem_size + k]);
+            }
+        }
+
+        PAIRS_DEBUG("\b]\n");
+    }
+    */
+}
+
+void PairsSimulation::fillCommunicationArrays(int *neighbor_ranks, int *pbc, real_t *subdom) {
+    this->getDomainPartitioner()->fillArrays(neighbor_ranks, pbc, subdom);
+}
+
+}
diff --git a/runtime/pairs.hpp b/runtime/pairs.hpp
index 3c37795dc327780f1b43c0a6366d8d52fc10c28f..7c5c80d65503466a8c2f937e18a62e81d64fdd9a 100644
--- a/runtime/pairs.hpp
+++ b/runtime/pairs.hpp
@@ -1,264 +1,24 @@
 #include <algorithm>
 #include <cmath>
-#include <iostream>
 #include <memory>
 #include <vector>
 //---
-#ifdef PAIRS_TARGET_CUDA
-#   include "devices/cuda.hpp"
-#else
-#   include "devices/dummy.hpp"
-#endif
-
+#include "array.hpp"
+#include "device_flags.hpp"
+#include "pairs_common.hpp"
+#include "property.hpp"
+#include "vector3.hpp"
+#include "devices/device.hpp"
 #include "domain/regular_6d_stencil.hpp"
 
 #pragma once
 
-#ifdef DEBUG
-#   include <assert.h>
-#   define PAIRS_DEBUG(...)     fprintf(stderr, __VA_ARGS__)
-#   define PAIRS_ASSERT(a)      assert(a)
-#   define PAIRS_EXCEPTION(a)
-#else
-#   define PAIRS_DEBUG(...)
-#   define PAIRS_ASSERT(a)
-#   define PAIRS_EXCEPTION(a)
-#endif
-
-#define PAIRS_ERROR(...)        fprintf(stderr, __VA_ARGS__)
-#define MIN(a,b)                ((a) < (b) ? (a) : (b))
-#define MAX(a,b)                ((a) > (b) ? (a) : (b))
-
 namespace pairs {
 
-typedef int array_t;
-typedef int property_t;
-typedef int layout_t;
-
-enum PropertyType {
-    Prop_Invalid = -1,
-    Prop_Integer = 0,
-    Prop_Float,
-    Prop_Vector
-};
-
-enum DataLayout {
-    Invalid = -1,
-    AoS = 0,
-    SoA
-};
-
-enum DomainPartitioning {
-    DimRanges = 0,
-    BoxList,
-};
-
-template<typename real_t>
-class Vector3 {
-private:
-    real_t x, y, z;
-
-public:
-    Vector3() : x((real_t) 0.0), y((real_t) 0.0), z((real_t) 0.0) {}
-    Vector3(real_t v) : x(v), y(v), z(v) {}
-    Vector3(real_t x_, real_t y_, real_t z_) : x(x_), y(y_), z(z_) {}
-};
-
-class Array {
-protected:
-    array_t id;
-    std::string name;
-    void *h_ptr, *d_ptr;
-    size_t size;
-    bool is_static;
-
-public:
-    Array(array_t id_, std::string name_, void *h_ptr_, void *d_ptr_, size_t size_, bool is_static_ = false) :
-        id(id_),
-        name(name_),
-        h_ptr(h_ptr_),
-        d_ptr(d_ptr_),
-        size(size_),
-        is_static(is_static_) {
-
-        PAIRS_ASSERT(size_ > 0);
-    }
-
-    property_t getId() { return id; }
-    std::string getName() { return name; }
-    void *getHostPointer() { return h_ptr; }
-    void *getDevicePointer() { return d_ptr; }
-    void setPointers(void *h_ptr_, void *d_ptr_) { h_ptr = h_ptr_, d_ptr = d_ptr_; }
-    void setSize(size_t size_) { size = size_; }
-    size_t getSize() { return size; };
-    bool isStatic() { return is_static; }
-};
-
-class Property {
-protected:
-    property_t id;
-    std::string name;
-    void *h_ptr, *d_ptr;
-    PropertyType type;
-    layout_t layout;
-    size_t sx, sy;
-
-public:
-    /*Property(property_t id_, std::string name_, void *h_ptr_, void *d_ptr_, PropertyType type_, layout_t layout, size_t sx_) :
-        id(id_),
-        name(name_),
-        h_ptr(h_ptr_),
-        d_ptr(d_ptr_),
-        type(type_),
-        layout(Invalid),
-        sx(sx_), sy(1) {
-
-        PAIRS_ASSERT(type != Prop_Invalid && layout_ != Invalid && sx_ > 0);
-    }*/
-
-    Property(property_t id_, std::string name_, void *h_ptr_, void *d_ptr_, PropertyType type_, layout_t layout_, size_t sx_, size_t sy_=1) :
-        id(id_),
-        name(name_),
-        h_ptr(h_ptr_),
-        d_ptr(d_ptr_),
-        type(type_),
-        layout(layout_),
-        sx(sx_), sy(sy_) {
-
-        PAIRS_ASSERT(type != Prop_Invalid && layout_ != Invalid && sx_ > 0 && sy_ > 0);
-    }
-
-    property_t getId() { return id; }
-    std::string getName() { return name; }
-    void *getHostPointer() { return h_ptr; }
-    void *getDevicePointer() { return d_ptr; }
-    void setPointers(void *h_ptr_, void *d_ptr_) { h_ptr = h_ptr_, d_ptr = d_ptr_; }
-    void setSizes(size_t sx_, size_t sy_) { sx = sx_, sy = sy_; }
-    size_t getTotalSize() { return sx * sy * getElemSize(); };
-    PropertyType getType() { return type; }
-    layout_t getLayout() { return layout; }
-    size_t getElemSize() {
-        return  (type == Prop_Integer) ? sizeof(int) :
-                (type == Prop_Float) ? sizeof(double) :
-                (type == Prop_Vector) ? sizeof(double) : 0;
-    }
-};
-
-class IntProperty : public Property {
-public:
-    inline int &operator()(int i) { return static_cast<int *>(h_ptr)[i]; }
-};
-
-class FloatProperty : public Property {
-public:
-    inline double &operator()(int i) { return static_cast<double *>(h_ptr)[i]; }
-};
-
-class VectorProperty : public Property {
-public:
-    double &operator()(int i, int j) {
-        PAIRS_ASSERT(type != Prop_Invalid && layout != Invalid && sx > 0 && sy > 0);
-        double *dptr = static_cast<double *>(h_ptr);
-        if(layout == AoS) { return dptr[i * sy + j]; }
-        if(layout == SoA) { return dptr[j * sx + i]; }
-        PAIRS_ERROR("VectorProperty::operator[]: Invalid data layout!");
-        return dptr[0];
-    }
-};
-
-class DeviceFlags {
-private:
-    unsigned long long int *hflags;
-    unsigned long long int *dflags;
-    int narrays;
-    int nflags;
-    static const int narrays_per_flag = 64;
-public:
-    DeviceFlags(int narrays_) : narrays(narrays_) {
-        nflags = std::ceil((double) narrays_ / (double) narrays_per_flag);
-        hflags = new unsigned long long int[nflags];
-        dflags = new unsigned long long int[nflags];
-
-        for(int i = 0; i < nflags; ++i) {
-            hflags[i] = 0xfffffffffffffffful;
-            dflags[i] = 0x0;
-        }
-    }
-
-    inline bool isHostFlagSet(int array_id) {
-        int flag_index = array_id / narrays_per_flag;
-        unsigned long long int bit = array_id % narrays_per_flag;
-        return (hflags[flag_index] & (1 << bit)) != 0;
-    }
-
-    inline void setHostFlag(int array_id) {
-        int flag_index = array_id / narrays_per_flag;
-        unsigned long long int bit = array_id % narrays_per_flag;
-        hflags[flag_index] |= 1 << bit;
-    }
-
-    inline void clearHostFlag(int array_id) {
-        int flag_index = array_id / narrays_per_flag;
-        unsigned long long int bit = array_id % narrays_per_flag;
-        hflags[flag_index] &= ~(1 << bit);
-    }
-
-    inline bool isDeviceFlagSet(int array_id) {
-        int flag_index = array_id / narrays_per_flag;
-        unsigned long long int bit = array_id % narrays_per_flag;
-        return (dflags[flag_index] & (1 << bit)) != 0;
-    }
-
-    inline void setDeviceFlag(int array_id) {
-        int flag_index = array_id / narrays_per_flag;
-        unsigned long long int bit = array_id % narrays_per_flag;
-        dflags[flag_index] |= 1 << bit;
-    }
-
-    inline void clearDeviceFlag(int array_id) {
-        int flag_index = array_id / narrays_per_flag;
-        unsigned long long int bit = array_id % narrays_per_flag;
-        dflags[flag_index] &= ~(1 << bit);
-    }
-
-    void printFlags() {
-        fprintf(stderr, "hflags = ");
-        for(int i = 0; i < nflags; i++) {
-            for(int b = 63; b >= 0; b--) {
-                if(hflags[i] & (1 << b)) {
-                    fprintf(stderr, "1");
-                } else {
-                    fprintf(stderr, "0");
-                }
-            }
-        }
-
-        fprintf(stderr, "\n");
-        fprintf(stderr, "dflags = ");
-        for(int i = 0; i < nflags; i++) {
-            for(int b = 63; b >= 0; b--) {
-                if(dflags[i] & (1 << b)) {
-                    fprintf(stderr, "1");
-                } else {
-                    fprintf(stderr, "0");
-                }
-            }
-        }
-
-        fprintf(stderr, "\n");
-    }
-
-    ~DeviceFlags() {
-        delete[] hflags;
-        delete[] dflags;
-    }
-};
-
-template<int ndims>
 class PairsSimulation {
 private:
-    Regular6DStencil<ndims> *dom_part;
-    //DomainPartitioner<ndims> *dom_part;
+    Regular6DStencil *dom_part;
+    //DomainPartitioner *dom_part;
     std::vector<Property> properties;
     std::vector<Array> arrays;
     DeviceFlags *prop_flags, *array_flags;
@@ -277,179 +37,32 @@ public:
         delete array_flags;
     }
 
-    void initDomain(int *argc, char ***argv, real_t xmin, real_t xmax, real_t ymin, real_t ymax, real_t zmin, real_t zmax) {
-        if(dom_part_type == DimRanges) {
-            dom_part = new Regular6DStencil<ndims>(xmin, xmax, ymin, ymax, zmin, zmax);
-        } else {
-            PAIRS_EXCEPTION("Domain partitioning type not implemented!\n");
-        }
-
-        dom_part->initialize(argc, argv);
-    }
-
-    Regular6DStencil<ndims> *getDomainPartitioner() { return dom_part; }
-
-    template<typename T_ptr>
-    void addArray(array_t id, std::string name, T_ptr **h_ptr, std::nullptr_t, size_t size) {
-        PAIRS_ASSERT(size > 0);
-
-        *h_ptr = (T_ptr *) malloc(size);
-        PAIRS_ASSERT(*h_ptr != nullptr);
-        addArray(Array(id, name, *h_ptr, nullptr, size, false));
-    }
-
-    template<typename T_ptr>
-    void addArray(array_t id, std::string name, T_ptr **h_ptr, T_ptr **d_ptr, size_t size) {
-        PAIRS_ASSERT(size > 0);
-
-        *h_ptr = (T_ptr *) malloc(size);
-        *d_ptr = (T_ptr *) pairs::device_alloc(size);
-        PAIRS_ASSERT(*h_ptr != nullptr && *d_ptr != nullptr);
-        addArray(Array(id, name, *h_ptr, *d_ptr, size, false));
-    }
-
-    template<typename T_ptr>
-    void addStaticArray(array_t id, std::string name, T_ptr *h_ptr, std::nullptr_t, size_t size) {
-        addArray(Array(id, name, h_ptr, nullptr, size, true));
-    }
-
-    template<typename T_ptr>
-    void addStaticArray(array_t id, std::string name, T_ptr *h_ptr, T_ptr *d_ptr, size_t size) {
-        addArray(Array(id, name, h_ptr, d_ptr, size, true));
-    }
-
-    void addArray(Array array) {
-        int id = array.getId();
-        auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
-        PAIRS_ASSERT(a == std::end(arrays));
-        arrays.push_back(array);
-    }
-
-    template<typename T_ptr>
-    void reallocArray(array_t id, T_ptr **h_ptr, std::nullptr_t, size_t size) {
-        // This should be a pointer (and not a reference) in order to be modified
-        auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
-        PAIRS_ASSERT(a != std::end(arrays));
-        PAIRS_ASSERT(size > 0);
-
-        *h_ptr = (T_ptr *) realloc(*h_ptr, size);
-        PAIRS_ASSERT(*h_ptr != nullptr);
-
-        a->setPointers(*h_ptr, nullptr);
-        a->setSize(size);
-    }
-
-    template<typename T_ptr>
-    void reallocArray(array_t id, T_ptr **h_ptr, T_ptr **d_ptr, size_t size) {
-        // This should be a pointer (and not a reference) in order to be modified
-        auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
-        PAIRS_ASSERT(a != std::end(arrays));
-        PAIRS_ASSERT(size > 0);
-
-        void *new_h_ptr = realloc(*h_ptr, size);
-        void *new_d_ptr = pairs::device_realloc(*d_ptr, size);
-        PAIRS_ASSERT(new_h_ptr != nullptr && new_d_ptr != nullptr);
-
-        a->setPointers(new_h_ptr, new_d_ptr);
-        a->setSize(size);
-
-        *h_ptr = (T_ptr *) new_h_ptr;
-        *d_ptr = (T_ptr *) new_d_ptr;
-        if(array_flags->isDeviceFlagSet(id)) {
-            copyArrayToDevice(id);
-        }
-    }
+    void initDomain(int *argc, char ***argv, real_t xmin, real_t xmax, real_t ymin, real_t ymax, real_t zmin, real_t zmax);
+    Regular6DStencil *getDomainPartitioner() { return dom_part; }
 
-    Array &getArray(array_t id) {
-        auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
-        PAIRS_ASSERT(a != std::end(arrays));
-        return *a;
-    }
+    template<typename T_ptr> void addArray(array_t id, std::string name, T_ptr **h_ptr, std::nullptr_t, size_t size);
+    template<typename T_ptr> void addArray(array_t id, std::string name, T_ptr **h_ptr, T_ptr **d_ptr, size_t size);
+    template<typename T_ptr> void addStaticArray(array_t id, std::string name, T_ptr *h_ptr, std::nullptr_t, size_t size);
+    template<typename T_ptr> void addStaticArray(array_t id, std::string name, T_ptr *h_ptr, T_ptr *d_ptr, size_t size);
+    void addArray(Array array);
 
-    Array &getArrayByName(std::string name) {
-        auto a = std::find_if(arrays.begin(), arrays.end(), [name](Array a) { return a.getName() == name; });
-        PAIRS_ASSERT(a != std::end(arrays));
-        return *a;
-    }
+    template<typename T_ptr> void reallocArray(array_t id, T_ptr **h_ptr, std::nullptr_t, size_t size);
+    template<typename T_ptr> void reallocArray(array_t id, T_ptr **h_ptr, T_ptr **d_ptr, size_t size);
 
-    template<typename T_ptr>
-    void addProperty(property_t id, std::string name, T_ptr **h_ptr, std::nullptr_t, PropertyType type, layout_t layout, size_t sx, size_t sy=1) {
-        size_t size = sx * sy * sizeof(T_ptr);
-        PAIRS_ASSERT(size > 0);
-
-        *h_ptr = (T_ptr *) malloc(size);
-        PAIRS_ASSERT(*h_ptr != nullptr);
-        addProperty(Property(id, name, *h_ptr, nullptr, type, layout, sx, sy));
-    }
+    Array &getArray(array_t id);
+    Array &getArrayByName(std::string name);
 
-    template<typename T_ptr>
-    void addProperty(property_t id, std::string name, T_ptr **h_ptr, T_ptr **d_ptr, PropertyType type, layout_t layout, size_t sx, size_t sy=1) {
-        size_t size = sx * sy * sizeof(T_ptr);
-        PAIRS_ASSERT(size > 0);
-
-        *h_ptr = (T_ptr *) malloc(size);
-        *d_ptr = (T_ptr *) pairs::device_alloc(size);
-        PAIRS_ASSERT(*h_ptr != nullptr && *d_ptr != nullptr);
-        addProperty(Property(id, name, *h_ptr, *d_ptr, type, layout, sx, sy));
-    }
-
-    void addProperty(Property prop) {
-        int id = prop.getId();
-        auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
-        PAIRS_ASSERT(p == std::end(properties));
-        properties.push_back(prop);
-    }
-
-    template<typename T_ptr>
-    void reallocProperty(property_t id, T_ptr **h_ptr, std::nullptr_t, size_t sx = 1, size_t sy = 1) {
-        // This should be a pointer (and not a reference) in order to be modified
-        auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
-        PAIRS_ASSERT(p != std::end(properties));
-
-        size_t size = sx * sy * p->getElemSize();
-        PAIRS_ASSERT(size > 0);
-
-        *h_ptr = (T_ptr *) realloc(*h_ptr, size);
-        PAIRS_ASSERT(*h_ptr != nullptr);
-
-        p->setPointers(*h_ptr, nullptr);
-        p->setSizes(sx, sy);
-    }
+    template<typename T_ptr> void addProperty(
+        property_t id, std::string name, T_ptr **h_ptr, std::nullptr_t, PropertyType type, layout_t layout, size_t sx, size_t sy = 1);
+    template<typename T_ptr> void addProperty(
+        property_t id, std::string name, T_ptr **h_ptr, T_ptr **d_ptr, PropertyType type, layout_t layout, size_t sx, size_t sy = 1);
+    void addProperty(Property prop);
 
-    template<typename T_ptr>
-    void reallocProperty(property_t id, T_ptr **h_ptr, T_ptr **d_ptr, size_t sx = 1, size_t sy = 1) {
-        // This should be a pointer (and not a reference) in order to be modified
-        auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
-        PAIRS_ASSERT(p != std::end(properties));
+    template<typename T_ptr> void reallocProperty(property_t id, T_ptr **h_ptr, std::nullptr_t, size_t sx = 1, size_t sy = 1);
+    template<typename T_ptr> void reallocProperty(property_t id, T_ptr **h_ptr, T_ptr **d_ptr, size_t sx = 1, size_t sy = 1);
 
-        size_t size = sx * sy * p->getElemSize();
-        PAIRS_ASSERT(size > 0);
-
-        void *new_h_ptr = realloc(*h_ptr, size);
-        void *new_d_ptr = pairs::device_realloc(*d_ptr, size);
-        PAIRS_ASSERT(new_h_ptr != nullptr && new_d_ptr != nullptr);
-
-        p->setPointers(new_h_ptr, new_d_ptr);
-        p->setSizes(sx, sy);
-
-        *h_ptr = (T_ptr *) new_h_ptr;
-        *d_ptr = (T_ptr *) new_d_ptr;
-        if(prop_flags->isDeviceFlagSet(id)) {
-            copyPropertyToDevice(id);
-        }
-    }
-
-    Property &getProperty(property_t id) {
-        auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
-        PAIRS_ASSERT(p != std::end(properties));
-        return *p;
-    }
-
-    Property &getPropertyByName(std::string name) {
-        auto p = std::find_if(properties.begin(), properties.end(), [name](Property p) { return p.getName() == name; });
-        PAIRS_ASSERT(p != std::end(properties));
-        return *p;
-    }
+    Property &getProperty(property_t id);
+    Property &getPropertyByName(std::string name);
 
     inline IntProperty &getAsIntegerProperty(Property &prop) { return static_cast<IntProperty&>(prop); }
     inline FloatProperty &getAsFloatProperty(Property &prop) { return static_cast<FloatProperty&>(prop); }
@@ -463,130 +76,164 @@ public:
     void clearArrayDeviceFlag(array_t id) { clearArrayDeviceFlag(getArray(id)); }
     void clearArrayDeviceFlag(Array &array) { array_flags->clearDeviceFlag(array.getId()); }
     void copyArrayToDevice(array_t id) { copyArrayToDevice(getArray(id)); }
-    void copyArrayToDevice(Array &array) {
-        int array_id = array.getId();
-        if(!array_flags->isDeviceFlagSet(array_id)) {
-            if(array.isStatic()) {
-                PAIRS_DEBUG("Copying static array %s to device\n", array.getName().c_str());
-                pairs::copy_static_symbol_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
-            } else {
-                PAIRS_DEBUG("Copying array %s to device\n", array.getName().c_str());
-                pairs::copy_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
-            }
-        }
-    }
+    void copyArrayToDevice(Array &array);
 
     void setArrayHostFlag(array_t id) { setArrayHostFlag(getArray(id)); }
     void setArrayHostFlag(Array &array) { array_flags->setHostFlag(array.getId()); }
     void clearArrayHostFlag(array_t id) { clearArrayHostFlag(getArray(id)); }
     void clearArrayHostFlag(Array &array) { array_flags->clearHostFlag(array.getId()); }
     void copyArrayToHost(array_t id) { copyArrayToHost(getArray(id)); }
-    void copyArrayToHost(Array &array) {
-        int array_id = array.getId();
-        if(!array_flags->isHostFlagSet(array_id)) {
-            if(array.isStatic()) {
-                PAIRS_DEBUG("Copying static array %s to host\n", array.getName().c_str());
-                pairs::copy_static_symbol_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
-            } else {
-                PAIRS_DEBUG("Copying array %s to host\n", array.getName().c_str());
-                pairs::copy_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
-            }
-        }
-    }
+    void copyArrayToHost(Array &array);
 
     void setPropertyDeviceFlag(property_t id) { setPropertyDeviceFlag(getProperty(id)); }
     void setPropertyDeviceFlag(Property &prop) { prop_flags->setDeviceFlag(prop.getId()); }
     void clearPropertyDeviceFlag(property_t id) { clearPropertyDeviceFlag(getProperty(id)); }
     void clearPropertyDeviceFlag(Property &prop) { prop_flags->clearDeviceFlag(prop.getId()); }
     void copyPropertyToDevice(property_t id) { copyPropertyToDevice(getProperty(id)); }
-    void copyPropertyToDevice(Property &prop) {
-        if(!prop_flags->isDeviceFlagSet(prop.getId())) {
-            PAIRS_DEBUG("Copying property %s to device\n", prop.getName().c_str());
-            pairs::copy_to_device(prop.getHostPointer(), prop.getDevicePointer(), prop.getTotalSize());
-        }
-    }
+    void copyPropertyToDevice(Property &prop);
 
     void setPropertyHostFlag(property_t id) { setPropertyHostFlag(getProperty(id)); }
     void setPropertyHostFlag(Property &prop) { prop_flags->setHostFlag(prop.getId()); }
     void clearPropertyHostFlag(property_t id) { clearPropertyHostFlag(getProperty(id)); }
     void clearPropertyHostFlag(Property &prop) { prop_flags->clearHostFlag(prop.getId()); }
     void copyPropertyToHost(property_t id) { copyPropertyToHost(getProperty(id)); }
-    void copyPropertyToHost(Property &prop) {
-        if(!prop_flags->isHostFlagSet(prop.getId())) {
-            PAIRS_DEBUG("Copying property %s to host\n", prop.getName().c_str());
-            pairs::copy_to_host(prop.getDevicePointer(), prop.getHostPointer(), prop.getTotalSize());
-        }
-    }
-
-    void communicateSizes(int dim, const int *send_sizes, int *recv_sizes) {
-        this->getDomainPartitioner()->communicateSizes(dim, send_sizes, recv_sizes);
-        PAIRS_DEBUG("send_sizes=[%d, %d], recv_sizes=[%d, %d]\n", send_sizes[dim * 2 + 0], send_sizes[dim * 2 + 1], recv_sizes[dim * 2 + 0], recv_sizes[dim * 2 + 1]);
-    }
+    void copyPropertyToHost(Property &prop);
 
+    void communicateSizes(int dim, const int *send_sizes, int *recv_sizes);
     void communicateData(
         int dim, int elem_size,
         const real_t *send_buf, const int *send_offsets, const int *nsend,
-        real_t *recv_buf, const int *recv_offsets, const int *nrecv) {
-
-        this->getDomainPartitioner()->communicateData(dim, elem_size, send_buf, send_offsets, nsend, recv_buf, recv_offsets, nrecv);
-
-        /*
-        // Debug messages
-        const int elems_to_print = 5;
-
-        // Send buffer debug
-        for(int i = 0; i < 2; i++) {
-            int nsnd = nsend[dim * 2 + i];
-
-            PAIRS_DEBUG("send_buf=[");
-            for(int j = 0; j < MIN(elems_to_print, nsnd); j++) {
-                for(int k = 0; k < elem_size; k++) {
-                    PAIRS_DEBUG("%f,", send_buf[(send_offsets[dim * 2 + i] + j) * elem_size + k]);
-                }
-            }
-
-            if(elems_to_print * 2 < nsnd) {
-                PAIRS_DEBUG("\b ... ");
-            }
-
-            for(int j = MAX(elems_to_print, nsnd - elems_to_print); j < nsnd; j++) {
-                for(int k = 0; k < elem_size; k++) {
-                    PAIRS_DEBUG("%f,", send_buf[(send_offsets[dim * 2 + i] + j) * elem_size + k]);
-                }
-            }
-
-            PAIRS_DEBUG("\b]\n");
-        }
-
-        // Receive buffer debug
-        for(int i = 0; i < 2; i++) {
-            int nrec = nrecv[dim * 2 + i];
-
-            PAIRS_DEBUG("recv_buf=[");
-            for(int j = 0; j < MIN(elems_to_print, nrec); j++) {
-                for(int k = 0; k < elem_size; k++) {
-                    PAIRS_DEBUG("%f,", recv_buf[(recv_offsets[dim * 2 + i] + j) * elem_size + k]);
-                }
-            }
-
-            if(elems_to_print * 2 < nrec) {
-                PAIRS_DEBUG("\b ... ");
-            }
-
-            for(int j = MAX(elems_to_print, nrec - elems_to_print); j < nrec; j++) {
-                for(int k = 0; k < elem_size; k++) {
-                    PAIRS_DEBUG("%f,", recv_buf[(recv_offsets[dim * 2 + i] + j) * elem_size + k]);
-                }
-            }
-
-            PAIRS_DEBUG("\b]\n");
-        }
-        */
+        real_t *recv_buf, const int *recv_offsets, const int *nrecv);
+
+    void fillCommunicationArrays(int neighbor_ranks[], int pbc[], real_t subdom[]);
+};
+
+template<typename T_ptr>
+void PairsSimulation::addArray(array_t id, std::string name, T_ptr **h_ptr, std::nullptr_t, size_t size) {
+    PAIRS_ASSERT(size > 0);
+
+    *h_ptr = (T_ptr *) malloc(size);
+    PAIRS_ASSERT(*h_ptr != nullptr);
+    addArray(Array(id, name, *h_ptr, nullptr, size, false));
+}
+
+template<typename T_ptr>
+void PairsSimulation::addArray(array_t id, std::string name, T_ptr **h_ptr, T_ptr **d_ptr, size_t size) {
+    PAIRS_ASSERT(size > 0);
+
+    *h_ptr = (T_ptr *) malloc(size);
+    *d_ptr = (T_ptr *) pairs::device_alloc(size);
+    PAIRS_ASSERT(*h_ptr != nullptr && *d_ptr != nullptr);
+    addArray(Array(id, name, *h_ptr, *d_ptr, size, false));
+}
+
+template<typename T_ptr>
+void PairsSimulation::addStaticArray(array_t id, std::string name, T_ptr *h_ptr, std::nullptr_t, size_t size) {
+    addArray(Array(id, name, h_ptr, nullptr, size, true));
+}
+
+template<typename T_ptr>
+void PairsSimulation::addStaticArray(array_t id, std::string name, T_ptr *h_ptr, T_ptr *d_ptr, size_t size) {
+    addArray(Array(id, name, h_ptr, d_ptr, size, true));
+}
+
+template<typename T_ptr>
+void PairsSimulation::reallocArray(array_t id, T_ptr **h_ptr, std::nullptr_t, size_t size) {
+    // This should be a pointer (and not a reference) in order to be modified
+    auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
+    PAIRS_ASSERT(a != std::end(arrays));
+    PAIRS_ASSERT(size > 0);
+
+    *h_ptr = (T_ptr *) realloc(*h_ptr, size);
+    PAIRS_ASSERT(*h_ptr != nullptr);
+
+    a->setPointers(*h_ptr, nullptr);
+    a->setSize(size);
+}
+
+template<typename T_ptr>
+void PairsSimulation::reallocArray(array_t id, T_ptr **h_ptr, T_ptr **d_ptr, size_t size) {
+    // This should be a pointer (and not a reference) in order to be modified
+    auto a = std::find_if(arrays.begin(), arrays.end(), [id](Array a) { return a.getId() == id; });
+    PAIRS_ASSERT(a != std::end(arrays));
+    PAIRS_ASSERT(size > 0);
+
+    void *new_h_ptr = realloc(*h_ptr, size);
+    void *new_d_ptr = pairs::device_realloc(*d_ptr, size);
+    PAIRS_ASSERT(new_h_ptr != nullptr && new_d_ptr != nullptr);
+
+    a->setPointers(new_h_ptr, new_d_ptr);
+    a->setSize(size);
+
+    *h_ptr = (T_ptr *) new_h_ptr;
+    *d_ptr = (T_ptr *) new_d_ptr;
+    if(array_flags->isDeviceFlagSet(id)) {
+        copyArrayToDevice(id);
     }
+}
+
+template<typename T_ptr>
+void PairsSimulation::addProperty(
+    property_t id, std::string name, T_ptr **h_ptr, std::nullptr_t, PropertyType type, layout_t layout, size_t sx, size_t sy) {
 
-    void fillCommunicationArrays(int neighbor_ranks[], int pbc[], real_t subdom[]) {
-        this->getDomainPartitioner()->fillArrays(neighbor_ranks, pbc, subdom);
+    size_t size = sx * sy * sizeof(T_ptr);
+    PAIRS_ASSERT(size > 0);
+
+    *h_ptr = (T_ptr *) malloc(size);
+    PAIRS_ASSERT(*h_ptr != nullptr);
+    addProperty(Property(id, name, *h_ptr, nullptr, type, layout, sx, sy));
+}
+
+template<typename T_ptr>
+void PairsSimulation::addProperty(
+    property_t id, std::string name, T_ptr **h_ptr, T_ptr **d_ptr, PropertyType type, layout_t layout, size_t sx, size_t sy) {
+
+    size_t size = sx * sy * sizeof(T_ptr);
+    PAIRS_ASSERT(size > 0);
+
+    *h_ptr = (T_ptr *) malloc(size);
+    *d_ptr = (T_ptr *) pairs::device_alloc(size);
+    PAIRS_ASSERT(*h_ptr != nullptr && *d_ptr != nullptr);
+    addProperty(Property(id, name, *h_ptr, *d_ptr, type, layout, sx, sy));
+}
+
+template<typename T_ptr>
+void PairsSimulation::reallocProperty(property_t id, T_ptr **h_ptr, std::nullptr_t, size_t sx, size_t sy) {
+    // This should be a pointer (and not a reference) in order to be modified
+    auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
+    PAIRS_ASSERT(p != std::end(properties));
+
+    size_t size = sx * sy * p->getElemSize();
+    PAIRS_ASSERT(size > 0);
+
+    *h_ptr = (T_ptr *) realloc(*h_ptr, size);
+    PAIRS_ASSERT(*h_ptr != nullptr);
+
+    p->setPointers(*h_ptr, nullptr);
+    p->setSizes(sx, sy);
+}
+
+template<typename T_ptr>
+void PairsSimulation::reallocProperty(property_t id, T_ptr **h_ptr, T_ptr **d_ptr, size_t sx, size_t sy) {
+    // This should be a pointer (and not a reference) in order to be modified
+    auto p = std::find_if(properties.begin(), properties.end(), [id](Property p) { return p.getId() == id; });
+    PAIRS_ASSERT(p != std::end(properties));
+
+    size_t size = sx * sy * p->getElemSize();
+    PAIRS_ASSERT(size > 0);
+
+    void *new_h_ptr = realloc(*h_ptr, size);
+    void *new_d_ptr = pairs::device_realloc(*d_ptr, size);
+    PAIRS_ASSERT(new_h_ptr != nullptr && new_d_ptr != nullptr);
+
+    p->setPointers(new_h_ptr, new_d_ptr);
+    p->setSizes(sx, sy);
+
+    *h_ptr = (T_ptr *) new_h_ptr;
+    *d_ptr = (T_ptr *) new_d_ptr;
+    if(prop_flags->isDeviceFlagSet(id)) {
+        copyPropertyToDevice(id);
     }
-};
+}
 
 }
diff --git a/runtime/pairs_common.hpp b/runtime/pairs_common.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e74453c60692b204650f41333124a3ca693a75e7
--- /dev/null
+++ b/runtime/pairs_common.hpp
@@ -0,0 +1,41 @@
+#include <iostream>
+
+#pragma once
+
+typedef double real_t;
+typedef int array_t;
+typedef int property_t;
+typedef int layout_t;
+
+enum PropertyType {
+    Prop_Invalid = -1,
+    Prop_Integer = 0,
+    Prop_Float,
+    Prop_Vector
+};
+
+enum DataLayout {
+    Invalid = -1,
+    AoS = 0,
+    SoA
+};
+
+enum DomainPartitioning {
+    DimRanges = 0,
+    BoxList,
+};
+
+#ifdef DEBUG
+#   include <assert.h>
+#   define PAIRS_DEBUG(...)     fprintf(stderr, __VA_ARGS__)
+#   define PAIRS_ASSERT(a)      assert(a)
+#   define PAIRS_EXCEPTION(a)
+#else
+#   define PAIRS_DEBUG(...)
+#   define PAIRS_ASSERT(a)
+#   define PAIRS_EXCEPTION(a)
+#endif
+
+#define PAIRS_ERROR(...)        fprintf(stderr, __VA_ARGS__)
+#define MIN(a,b)                ((a) < (b) ? (a) : (b))
+#define MAX(a,b)                ((a) > (b) ? (a) : (b))
diff --git a/runtime/property.hpp b/runtime/property.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4690a8f14a435045867a9baa104640ccd2e87f1f
--- /dev/null
+++ b/runtime/property.hpp
@@ -0,0 +1,67 @@
+#include "pairs_common.hpp"
+
+#pragma once
+
+namespace pairs {
+
+class Property {
+protected:
+    property_t id;
+    std::string name;
+    void *h_ptr, *d_ptr;
+    PropertyType type;
+    layout_t layout;
+    size_t sx, sy;
+
+public:
+    Property(property_t id_, std::string name_, void *h_ptr_, void *d_ptr_, PropertyType type_, layout_t layout_, size_t sx_, size_t sy_=1) :
+        id(id_),
+        name(name_),
+        h_ptr(h_ptr_),
+        d_ptr(d_ptr_),
+        type(type_),
+        layout(layout_),
+        sx(sx_), sy(sy_) {
+
+        PAIRS_ASSERT(type != Prop_Invalid && layout_ != Invalid && sx_ > 0 && sy_ > 0);
+    }
+
+    property_t getId() { return id; }
+    std::string getName() { return name; }
+    void *getHostPointer() { return h_ptr; }
+    void *getDevicePointer() { return d_ptr; }
+    void setPointers(void *h_ptr_, void *d_ptr_) { h_ptr = h_ptr_, d_ptr = d_ptr_; }
+    void setSizes(size_t sx_, size_t sy_) { sx = sx_, sy = sy_; }
+    size_t getTotalSize() { return sx * sy * getElemSize(); };
+    PropertyType getType() { return type; }
+    layout_t getLayout() { return layout; }
+    size_t getElemSize() {
+        return  (type == Prop_Integer) ? sizeof(int) :
+                (type == Prop_Float) ? sizeof(double) :
+                (type == Prop_Vector) ? sizeof(double) : 0;
+    }
+};
+
+class IntProperty : public Property {
+public:
+    inline int &operator()(int i) { return static_cast<int *>(h_ptr)[i]; }
+};
+
+class FloatProperty : public Property {
+public:
+    inline double &operator()(int i) { return static_cast<double *>(h_ptr)[i]; }
+};
+
+class VectorProperty : public Property {
+public:
+    double &operator()(int i, int j) {
+        PAIRS_ASSERT(type != Prop_Invalid && layout != Invalid && sx > 0 && sy > 0);
+        double *dptr = static_cast<double *>(h_ptr);
+        if(layout == AoS) { return dptr[i * sy + j]; }
+        if(layout == SoA) { return dptr[j * sx + i]; }
+        PAIRS_ERROR("VectorProperty::operator[]: Invalid data layout!");
+        return dptr[0];
+    }
+};
+
+}
diff --git a/runtime/read_from_file.hpp b/runtime/read_from_file.hpp
index 7a681dc0b564bd005a357e6b7a6694155a015990..ba80da72897937e8db58fea068b6268fdc0cddf0 100644
--- a/runtime/read_from_file.hpp
+++ b/runtime/read_from_file.hpp
@@ -4,13 +4,13 @@
 #include <sstream>
 //---
 #include "pairs.hpp"
+#include "pairs_common.hpp"
 
 #pragma once
 
 namespace pairs {
 
-template<int ndims>
-void read_grid_data(PairsSimulation<ndims> *ps, const char *filename, double *grid_buffer) {
+void read_grid_data(PairsSimulation *ps, const char *filename, double *grid_buffer) {
     std::ifstream in_file(filename, std::ifstream::in);
     std::string line;
 
@@ -21,7 +21,7 @@ void read_grid_data(PairsSimulation<ndims> *ps, const char *filename, double *gr
         int i = 0;
 
         while(std::getline(line_stream, in0, ',')) {
-            PAIRS_ASSERT(i < ndims * 2);
+            //PAIRS_ASSERT(i < ndims * 2);
             grid_buffer[i] = std::stod(in0);
             i++;
         }
@@ -30,8 +30,7 @@ void read_grid_data(PairsSimulation<ndims> *ps, const char *filename, double *gr
     }
 }
 
-template<int ndims>
-size_t read_particle_data(PairsSimulation<ndims> *ps, const char *filename, const property_t properties[], size_t nprops) {
+size_t read_particle_data(PairsSimulation *ps, const char *filename, const property_t properties[], size_t nprops) {
     std::ifstream in_file(filename, std::ifstream::in);
     std::string line;
     size_t n = 0;
diff --git a/runtime/vector3.hpp b/runtime/vector3.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..15277183eb15835e0e625c6f92ef65cccfd6620e
--- /dev/null
+++ b/runtime/vector3.hpp
@@ -0,0 +1,18 @@
+#include "pairs_common.hpp"
+
+#pragma once
+
+namespace pairs {
+
+template<typename real_t>
+class Vector3 {
+private:
+    real_t x, y, z;
+
+public:
+    Vector3() : x((real_t) 0.0), y((real_t) 0.0), z((real_t) 0.0) {}
+    Vector3(real_t v) : x(v), y(v), z(v) {}
+    Vector3(real_t x_, real_t y_, real_t z_) : x(x_), y(y_), z(z_) {}
+};
+
+}
diff --git a/runtime/vtk.hpp b/runtime/vtk.hpp
index eb10bce12a4c7902078629062371323c3d12200e..e2041a787dd0468e4a37995721f1e731ebc52c05 100644
--- a/runtime/vtk.hpp
+++ b/runtime/vtk.hpp
@@ -8,8 +8,7 @@
 
 namespace pairs {
 
-template <int ndims>
-void vtk_write_data(PairsSimulation<ndims> *ps, const char *filename, int start, int end, int timestep) {
+void vtk_write_data(PairsSimulation *ps, const char *filename, int start, int end, int timestep) {
     std::string output_filename(filename);
     auto masses = ps->getAsFloatProperty(ps->getPropertyByName("mass"));
     auto positions = ps->getAsVectorProperty(ps->getPropertyByName("position"));
diff --git a/src/pairs/code_gen/cgen.py b/src/pairs/code_gen/cgen.py
index bd41f7ac9a5fbd20fedb132f5fe67d8d2fbfee07..4efca1aae8ce954ee517f502fdc90537db8b683a 100644
--- a/src/pairs/code_gen/cgen.py
+++ b/src/pairs/code_gen/cgen.py
@@ -58,10 +58,10 @@ class CGen:
         self.print("#include \"runtime/read_from_file.hpp\"")
         self.print("#include \"runtime/vtk.hpp\"")
 
-        if self.target.is_gpu():
-            self.print("#include \"runtime/devices/cuda.hpp\"")
-        else:
-            self.print("#include \"runtime/devices/dummy.hpp\"")
+        #if self.target.is_gpu():
+        #    self.print("#include \"runtime/devices/cuda.hpp\"")
+        #else:
+        #    self.print("#include \"runtime/devices/dummy.hpp\"")
 
         self.print("")
         self.print("using namespace pairs;")
@@ -91,7 +91,7 @@ class CGen:
             nprops = module.sim.properties.nprops()
             narrays = module.sim.arrays.narrays()
             self.print("int main(int argc, char **argv) {")
-            self.print(f"    PairsSimulation<{ndims}> *pairs = new PairsSimulation<{ndims}>({nprops}, {narrays}, DimRanges);")
+            self.print(f"    PairsSimulation *pairs = new PairsSimulation({nprops}, {narrays}, DimRanges);")
             self.generate_statement(module.block)
             self.print("    return 0;")
             self.print("}")