[ufo-core] 04/08: Imported Upstream version 0.5.3

Frédéric-Emmanuel Picca picca at moszumanska.debian.org
Sat Nov 30 20:54:12 UTC 2013


This is an automated email from the git hooks/post-receive script.

picca pushed a commit to branch master
in repository ufo-core.

commit a143cf2354d98606fec552fd4853545f1818efff
Author: Picca Frédéric-Emmanuel <picca at debian.org>
Date:   Mon Nov 11 19:50:08 2013 +0100

    Imported Upstream version 0.5.3
---
 .gitignore                               |   2 +-
 CMakeLists.txt                           | 190 ++++++--
 NEWS                                     | 204 ++++++++
 README.md                                |  21 +
 common/cmake/FindOpenCL.cmake            |   1 +
 docs/CMakeLists.txt                      |   8 +-
 docs/manual/_templates/indexcontent.html |  48 --
 docs/manual/_templates/indexsidebar.html |   5 -
 docs/manual/_templates/layout.html       |   5 -
 docs/manual/changelog.rst                |   4 +
 docs/manual/conf.py.in                   |  14 +-
 docs/manual/contents.rst                 |  17 -
 docs/manual/index.rst                    |  35 ++
 docs/manual/using/cluster.rst            |  18 +
 docs/manual/using/filters.rst            |  29 +-
 docs/manual/whatsnew/0.1.rst             |  12 -
 docs/manual/whatsnew/0.2.rst             |  69 ---
 docs/manual/whatsnew/0.3.rst             |  31 --
 docs/manual/whatsnew/index.rst           |  15 -
 python/CMakeLists.txt                    |   2 +-
 python/setup.py.in                       |  78 +++-
 python/ufonp.c                           | 136 ++++++
 python/ufotools/__init__.py              |  27 --
 tests/CMakeLists.txt                     |  31 +-
 tests/test-buffer.c                      | 130 ++++++
 tests/test-config.c                      |  31 +-
 tests/test-graph.c                       | 193 ++++++--
 tests/test-mpi-remote-node.c             | 126 +++++
 tests/test-profiler.c                    |   3 +-
 tests/test-remote-node.c                 |  84 ++++
 tests/test-suite.c                       |  19 +-
 tests/test-suite.h                       |   4 +
 tests/test-zmq-messenger.c               | 111 +++++
 tools/CMakeLists.txt                     |   7 +-
 tools/runjson.c                          | 134 +++++-
 tools/ufod.c                             | 374 ++-------------
 ufo/CMakeLists.txt                       | 151 +++---
 ufo/config.h.in                          |   4 +-
 ufo/ufo-arch-graph.c                     |  17 +-
 ufo/ufo-basic-ops.c                      | 681 +++++++++++++++++++++++++++
 ufo/ufo-basic-ops.cl                     | 326 +++++++++++++
 ufo/ufo-basic-ops.h                      |  89 ++++
 ufo/ufo-buffer.c                         | 679 +++++++++++++++++++++------
 ufo/ufo-buffer.h                         |  24 +-
 ufo/ufo-config.c                         |  96 ++--
 ufo/ufo-config.h                         |  26 +-
 ufo/ufo-cpu-task-iface.c                 |  17 -
 ufo/ufo-cpu-task-iface.h                 |  34 +-
 ufo/ufo-daemon.c                         | 586 +++++++++++++++++++++++
 ufo/ufo-daemon.h                         |  72 +++
 ufo/ufo-gpu-task-iface.c                 |  35 +-
 ufo/ufo-gpu-task-iface.h                 |  40 +-
 ufo/ufo-graph.c                          | 302 +++++++++---
 ufo/ufo-graph.h                          |  11 +-
 ufo/ufo-group.c                          |  18 +-
 ufo/ufo-group.h                          |   1 +
 ufo/ufo-input-task.c                     |  70 +--
 ufo/ufo-messenger-iface.c                | 119 +++++
 ufo/ufo-messenger-iface.h                | 152 ++++++
 ufo/ufo-mpi-messenger.c                  | 261 +++++++++++
 ufo/ufo-mpi-messenger.h                  |  70 +++
 ufo/ufo-node.c                           |  81 +++-
 ufo/ufo-node.h                           |   2 +
 ufo/ufo-output-task.c                    |   2 +-
 ufo/ufo-plugin-manager.c                 | 200 +++++---
 ufo/ufo-plugin-manager.h                 |  16 +-
 ufo/ufo-profiler.c                       | 185 +++++---
 ufo/ufo-profiler.h                       |  65 ++-
 ufo/ufo-remote-node.c                    | 333 ++++++-------
 ufo/ufo-remote-node.h                    |  55 +--
 ufo/ufo-resources.c                      | 772 ++++++++++++++++++++++---------
 ufo/ufo-resources.h                      |  24 +-
 ufo/ufo-scheduler.c                      | 605 ++++++++++++++++++------
 ufo/ufo-scheduler.h                      |  29 +-
 ufo/ufo-task-graph.c                     | 189 +++++---
 ufo/ufo-task-graph.h                     |  11 +-
 ufo/ufo-task-iface.h                     |  21 +-
 ufo/ufo-task-node.c                      |  92 +++-
 ufo/ufo-task-node.h                      |  10 +
 ufo/ufo-zmq-messenger.c                  | 309 +++++++++++++
 ufo/ufo-zmq-messenger.h                  |  70 +++
 ufo/ufo.h                                |   3 +
 ufo/ufo.pc.in                            |  12 +-
 ufo/zmq-shim.h                           |  22 +
 84 files changed, 7136 insertions(+), 2071 deletions(-)

diff --git a/.gitignore b/.gitignore
index 567609b..a5309e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-build/
+build*/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a608d96..911ecb2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,17 +2,21 @@ cmake_minimum_required(VERSION 2.6)
 project(ufo C)
 
 set(TARNAME "libufo")
+
+set(UFO_DESCRIPTION "UFO high-speed image processing core library")
+set(UFO_DESCRIPTION_SUMMARY "UFO high-speed image processing core library")
+
+#{{{ Library version
 set(UFO_VERSION_MAJOR "0")
-set(UFO_VERSION_MINOR "3")
-set(UFO_VERSION_PATCH "1")
+set(UFO_VERSION_MINOR "5")
+set(UFO_VERSION_PATCH "3")
 set(UFO_GIR_VERSION "${UFO_VERSION_MAJOR}.${UFO_VERSION_MINOR}")
 
 # increase UFO_SO_VERSION on each version that breaks ABI compatibility
-set(UFO_SO_VERSION "3")
-
-set(UFO_DESCRIPTION "UFO high-speed image processing core library")
-set(UFO_DESCRIPTION_SUMMARY "UFO high-speed image processing core library")
+set(UFO_SO_VERSION "4")
+#}}}
 
+#{{{ Package version
 set(PACKAGE_VERSION_MAJOR ${UFO_VERSION_MAJOR})
 set(PACKAGE_VERSION_MINOR ${UFO_VERSION_MINOR})
 set(PACKAGE_VERSION_PATCH ${UFO_VERSION_PATCH})
@@ -21,38 +25,114 @@ set(PACKAGE_NAME ${TARNAME})
 set(PACKAGE_TARNAME ${TARNAME})
 set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
 set(PACKAGE_BUGREPORT "http://ufo.kit.edu/ufo/newticket")
+#}}}
 
+#{{{ CMake
 enable_testing()
 
 set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/common/cmake")
+#}}}
 
-include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
-include_directories("${CMAKE_CURRENT_SOURCE_DIR}/ufo")
-include_directories("${CMAKE_CURRENT_BINARY_DIR}/ufo")
-
+find_program (MPICC "mpicc")
 
-# --- Options -----------------------------------------------------------------
+#{{{ Options
 option(WITH_PROFILING "Enable profiling" OFF)
+option(WITH_TESTS "Build test suite" ON)
+option(WITH_MPI "Build with MPI support" OFF)
+option(WITH_DEBUG "Build with DEBUG support" OFF)
+
 if (WITH_PROFILING)
     add_definitions("-pg")
     set(CMAKE_C_FLAGS "-pg")
 endif ()
 
+if (WITH_DEBUG)
+    add_definitions ("-DDEBUG")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -ggdb")
+endif ()
+
+if (WITH_MPI)
+    add_definitions ("-DMPI")
+    
+    if (NOT MPICC)
+        message (FATAL_ERROR "No mpicc compiler be found but MPI was explicitly enabled")
+    endif ()
+
+    set(CMAKE_C_COMPILER ${MPICC})
+endif ()
+#}}}
+
+#{{{ Installation directories
+
+# Set what the user passed
+set(UFO_PREFIX "${PREFIX}")
+set(UFO_EPREFIX "${EXEC_PREFIX}")
+set(UFO_INCLUDEDIR "${INCLUDEDIR}")
+set(UFO_BINDIR "${BINDIR}")
+set(UFO_LIBDIR "${LIBDIR}")
+set(UFO_DATAROOTDIR "${DATAROOTDIR}")
+set(UFO_DATADIR "${DATAROOTDIR}")
+set(UFO_PKGCONFIGDIR "${PKGCONFIGDIR}")
+set(UFO_GIRDIR "${GIRDIR}")
+set(UFO_TYPELIBDIR "${TYPELIBDIR}")
+
+# Fix in the same way configure does
+if(UFO_PREFIX STREQUAL "")
+    set(UFO_PREFIX "${CMAKE_INSTALL_PREFIX}")
+endif()
+
+if(UFO_EPREFIX STREQUAL "")
+    set(UFO_EPREFIX "${UFO_PREFIX}")
+endif()
+
+if(UFO_INCLUDEDIR STREQUAL "")
+    set(UFO_INCLUDEDIR "${UFO_PREFIX}/include")
+endif()
+
+if(UFO_BINDIR STREQUAL "")
+    set(UFO_BINDIR "${UFO_EPREFIX}/bin")
+endif()
+
+if(UFO_LIBDIR STREQUAL "")
+    set(UFO_LIBDIR "${UFO_EPREFIX}/lib")
+endif()
+
+if(UFO_DATAROOTDIR STREQUAL "")
+    set(UFO_DATAROOTDIR "${UFO_PREFIX}/share")
+endif()
 
-# --- Find packages and libraries ---------------------------------------------
+if(UFO_DATADIR STREQUAL "")
+    set(UFO_DATADIR "${UFO_DATAROOTDIR}")
+endif()
 
-# These packages are required in all sub-directories because Glib and GObject is
-# part of the API/ABI.
+if(UFO_PKGCONFIGDIR STREQUAL "")
+    set(UFO_PKGCONFIGDIR "${UFO_LIBDIR}/pkgconfig")
+endif()
+
+if(UFO_GIRDIR STREQUAL "")
+    set(UFO_GIRDIR "${UFO_DATADIR}/gir-1.0")
+endif()
+
+if(UFO_TYPELIBDIR STREQUAL "")
+    set(UFO_TYPELIBDIR "${UFO_LIBDIR}/girepository-1.0")
+endif()
+#}}}
+
+#{{{ Dependencies
+set(PKG_GLIB2_MIN_REQUIRED "2.22")
+set(PKG_JSON_GLIB_MIN_REQUIRED "0.7.6")
+set(PKG_ZMQ_MIN_REQUIRED "2.1")
 
 find_package(OpenCL REQUIRED)
 find_package(PkgConfig REQUIRED)
 
-pkg_check_modules(GLIB2 glib-2.0>=2.22 REQUIRED)
-pkg_check_modules(GOBJECT2 gobject-2.0>=2.22 REQUIRED)
-pkg_check_modules(GMODULE2 gmodule-2.0>=2.22 REQUIRED)
-pkg_check_modules(GTHREAD2 gthread-2.0>=2.22 REQUIRED)
-pkg_check_modules(JSON_GLIB json-glib-1.0 REQUIRED)
-pkg_check_modules(ZMQ libzmq>=3.2 REQUIRED)
+pkg_check_modules(GLIB2 glib-2.0>=${PKG_GLIB2_MIN_REQUIRED} REQUIRED)
+pkg_check_modules(GOBJECT2 gobject-2.0>=${PKG_GLIB2_MIN_REQUIRED} REQUIRED)
+pkg_check_modules(GMODULE2 gmodule-2.0>=${PKG_GLIB2_MIN_REQUIRED} REQUIRED)
+pkg_check_modules(GTHREAD2 gthread-2.0>=${PKG_GLIB2_MIN_REQUIRED} REQUIRED)
+pkg_check_modules(GIO2 gio-2.0>=${PKG_GLIB2_MIN_REQUIRED} REQUIRED)
+pkg_check_modules(JSON_GLIB json-glib-1.0>=${PKG_JSON_GLIB_MIN_REQUIRED} REQUIRED)
+pkg_check_modules(ZMQ libzmq>=${PKG_ZMQ_MIN_REQUIRED} REQUIRED)
 
 set(UFOCORE_DEPS
     ${OPENCL_LIBRARIES}
@@ -60,42 +140,60 @@ set(UFOCORE_DEPS
     ${GOBJECT2_LIBRARIES}
     ${GMODULE2_LIBRARIES}
     ${GTHREAD2_LIBRARIES}
+    ${GIO2_LIBRARIES}
     ${JSON_GLIB_LIBRARIES}
     ${ZMQ_LIBRARIES})
 
-
+#{{{ Add include directories
 include_directories(
+    ${CMAKE_CURRENT_SOURCE_DIR}
+    ${CMAKE_CURRENT_SOURCE_DIR}/ufo
     ${CMAKE_CURRENT_BINARY_DIR}
-    ${CMAKE_CURRENT_BINARY_DIR}/src
+    ${CMAKE_CURRENT_BINARY_DIR}/ufo
     ${GLIB2_INCLUDE_DIRS}
+    ${GIO2_INCLUDE_DIRS}
     ${OPENCL_INCLUDE_DIRS}
     ${JSON_GLIB_INCLUDE_DIRS}
     ${ZMQ_INCLUDE_DIRS})
-
-link_directories(
-    ${ZMQ_LIBRARY_DIRS}
-    ${JSON_GLIB_LIBRARY_DIRS})
-
-add_definitions("-std=c99 -pedantic -Wall -Wextra -fPIC")
-
-if (CMAKE_COMPILER_IS_GNUCC)
-    add_definitions("-Wmissing-prototypes -Wmissing-declarations -Wshadow
-    -Wpointer-arith -Wcast-align -Wwrite-strings -Wredundant-decls -Wcast-qual
-    -Wnested-externs -Winline -Wno-long-long -Wconversion -Wstrict-prototypes")
-
-    add_definitions("-Wno-unused-parameter -Wno-missing-field-initializers")
+#}}}
+#}}}
+
+#{{{ Definitions
+add_definitions("-std=c99 -pedantic -Wall -Wextra -Werror -fPIC")
+
+if (CMAKE_COMPILER_IS_GNUCC OR ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang"))
+    add_definitions("-Wcast-align
+                     -Wcast-qual
+                     -Winline
+                     -Wmissing-declarations
+                     -Wmissing-prototypes
+                     -Wnested-externs
+                     -Wno-deprecated-declarations
+                     -Wno-long-long
+                     -Wno-missing-field-initializers
+                     -Wno-unused-parameter
+                     -Wpointer-arith
+                     -Wredundant-decls
+                     -Wshadow
+                     -Wstrict-prototypes
+                     -Wwrite-strings")
 endif()
 
 add_definitions(-DG_LOG_DOMAIN=\"Ufo\")
+#}}}
 
+#{{{ Subdirectories
 add_subdirectory(ufo)
 add_subdirectory(docs)
-#add_subdirectory(python)
-add_subdirectory(tests)
+add_subdirectory(python)
 add_subdirectory(tools)
 
+if (WITH_TESTS)
+    add_subdirectory(tests)
+endif()
+#}}}
 
-# --- Package generation ------------------------------------------------------
+#{{{ CPack
 set(CPACK_PACKAGE_DESCRIPTION ${UFO_DESCRIPTION})
 set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${UFO_DESCRIPTION_SUMMARY})
 set(CPACK_PACKAGE_NAME ${TARNAME})
@@ -110,10 +208,20 @@ set(VERSION ${PACKAGE_VERSION})
 
 set(CPACK_GENERATOR "DEB;RPM;")
 set(CPACK_SOURCE_GENERATOR "TGZ")
-set(CPACK_SOURCE_IGNORE_FILES "tags" ".bzr" ".swp" "~1~" ".git" ".xml")
+set(CPACK_SOURCE_IGNORE_FILES "/build/;.git/;tags;.swp;${CPACK_SOURCE_IGNORE_FILES}")
 set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PACKAGE_NAME}-${PACKAGE_VERSION}" CACHE INTERNAL "tarball basename")
 
-# --- Distro specific
-set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.3.6), libgcc1 (>= 1:4.1)")
+#{{{ Debian
+set(CPACK_DEBIAN_PACKAGE_DEPENDS
+    "libglib2.0-0 (>= ${PKG_GLIB2_MIN_REQUIRED}),
+     libjson-glib-1.0-0 (>= ${PKG_JSON_GLIB_MIN_REQUIRED}),
+     libzmq1 (>= ${PKG_ZMQ_MIN_REQUIRED})")
+#}}}
+
+#{{{ RPM
+set(CPACK_RPM_PACKAGE_AUTOREQPROV " no")
+set(CPACK_RPM_PACKAGE_REQUIRES "libjson-glib-1_0-0, libzmq1")
+#}}}
 
 include(CPack)
+#}}}
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..1fdaec4
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,204 @@
+=========
+Changelog
+=========
+
+Here you can see the full list of changes between each ufo-core release.
+
+
+Version 0.5
+===========
+
+Released on October 28th 2013.
+
+- Added MPI support as an alternative to ZMQ.
+- Added basic math operations for use with filters.
+- UFO can now be used reliably in a multithreaded Python context. That means,
+  calling ``ufo_scheduler_run`` in a Python thread will "just work". This change
+  allows run-time injection of NumPy buffers into the task graph.
+
+Developers
+----------
+- Add ``-DDEBUG`` when debug is enabled so we can #ifdef it
+- Add GLib version guards
+- ufo-core compiles with Clang
+- CMake 2.6 is used solely throughout the sources
+- Add convenience function ufo_buffer_new_with_size
+- Add a shim macros to support both zmq 2 and 3
+- Add ``UFO_USE_GPU`` env var to restrict to single GPU
+- Added ufo_resource_manager_get_cached_kernel that always returns the same
+  kernel object when given the same file and kernel name. Note, that you have to
+  guard it properly and do not call ``clSetKernelArg`` from multiple threads at
+  the same time.
+- Add profile tracing to produce a JSON trace event file, that can be read and
+  visualized with Google's Chrome browser. It can be enabled with
+  UfoScheduler::enable-tracing set to TRUE.
+
+Bug fixes
+---------
+- Fix #6: Don't use enum values as bit flags
+- Fix bug: no plugin name is sent to remote nodes
+- Fix copy segfault in when source has not alloc'd
+- Removed dependency on a C++ compiler
+- Fix reduction problem
+
+
+Version 0.4
+===========
+
+Released on July 18th 2013.
+
+Major changes
+-------------
+
+- Rewrote internal architecture for better scheduling.
+- Remove profiler levels and add more output
+- Implement input data partitioning: On clusters where distributed data access
+  is possible, we can achieve perfect linear scalability by partitioning the
+  input data set.
+- Install SIGTERM handler for cleanup of node server
+
+
+Features
+--------
+
+- Add ufo_task_graph_get_json_data
+- Streamline and simplify scheduling
+- Provide function to flatten graph
+- Provide graph copy functionality
+- Add node indices for copies
+- Add all paths as OpenCL include paths
+- Write out JSON version
+- Search in UFO_PLUGIN_PATH env var
+
+
+Bug fixes
+---------
+
+- Fix problems with AMD platforms
+- Fix timestamp readout
+- Fix potential single integer overflow
+- Exit when JSON tasks could not be found
+- Fix remote tasks getting stuck
+- Unref expanded nodes explicitly
+- Fix #189: don't copy nodes with more than one input
+- Fix #219: Warn instead of segfault
+- Fix annotation for older GI compiler
+- Fix problem with first remote data item
+- Fix platform selection
+- Fix problems with objects that are not unreffed
+- Refactor buffer and add support for #184
+- Refactor resources and fix #183
+- Fix buffer for broadcast operations
+
+
+Version 0.3
+===========
+
+Released on February 8th 2013.
+
+Major breakage
+--------------
+
+- A graph is now a simple data structure and not a specific graph of task nodes.
+  This is implemented by a TaskGraph.
+
+- Filters are now called TaskNodes and connected with
+  ``ufo_task_graph_connect_nodes`` and ``ufo_task_graph_connect_nodes_full``
+  respectively.
+
+
+Graph expansion
+---------------
+
+With 0.2, Using multiple GPUs was possible by manually splitting paths in the
+graph and assigning GPUs. Now, task graphs are automatically expanded depending
+on the number of available GPUs and remote processing slaves that are started
+with the newly added ``ufod`` server software.
+
+
+Minor improvements
+------------------
+
+- A ``deploy.sh`` script has been added for easier deployment of the software
+  stack. This is especially useful to install everything in the home directory
+  of the user, who only needs to setup ``LD_LIBRARY_PATH`` and
+  ``GI_TYPELIB_PATH`` correctly to run the software.
+
+
+Version 0.2
+===========
+
+Released on November 8th 2012.
+
+Major breakage
+--------------
+
+- Filters are now prefixed again with ``libfilter`` to allow introspected
+  documentation. Thus, any filter built for 0.1 cannot be used because they are
+  simply not found.
+
+- :c:func:`ufo_plugin_manager_get_filter` received a new third parameter
+  ``error`` that reports errors when opening and loading a UfoFilter from a
+  shared object.
+
+- :c:func:`ufo_resource_manager_add_program` is removed.
+
+- The kernel file name must be passed to :c:func:`ufo_resource_manager_get_kernel`.
+
+- The ``CHECK_ERROR`` macro defined in ``ufo-resource-manager.h`` was renamed to
+  ``CHECK_OPENCL_ERROR`` to better reflect its real purpose.
+
+- The old JSON specification has been changed to reflect the possibilities of
+  the current API. Thus, JSON files that worked under Ufo 0.1 cannot be read
+  with Ufo 0.2.
+
+- Removed the otherwise unused :c:func:`ufo_buffer_get_transfer_time` and
+  replaced this with the more flexible :c:func:`ufo_buffer_get_transfer_timer`.
+
+- Rename :c:func:`ufo_filter_initialize` to
+  :c:func:`ufo_filter_set_plugin_name` that reflects its true meaning.
+
+
+Scheduling
+~~~~~~~~~~
+
+A more scheduable way to run filters has been implemented with the virtual
+:c:func:`process_cpu` and :c:func:`process_gpu` methods. Contrary to the old
+way, they act on *one* working set at a time that is passed as an array of
+pointers to :c:type:`UfoBuffer`. Sometimes, a filter needs to setup data
+depending on the input size. For this reason, the virtual method
+:c:func:`initialize` takes a second parameter that is again a list of pointers
+to buffer objects.
+
+Moreover, the :c:type:`UfoScheduler` class has been added that is combining
+the work previously accomplished by :c:func:`ufo_filter_process` and
+:c:func:`ufo_graph_run`. The scheduler orchestrates the filters and
+assigns resources in a meaningful way.
+
+If written in the new kernel style, producer filters must return a boolean flag
+denoting if data was produced or not.
+
+
+General improvements
+--------------------
+
+- The manual was restructured considerably.
+
+- Saving graphs as JSON files has been added via
+  :c:func:`ufo_graph_save_to_json()`.
+
+- Filters can now wait until their properties satisfy a condition using
+  :c:func:`ufo_filter_wait_until`, see also :ref:`filters-block`.
+
+- A new method :c:func:`ufo_resource_manager_get_kernel_from_source` so that
+  filters can load kernels directly from source.
+
+- Streamlined error handling: Filters should not issue ``g_warnings`` or
+  ``g_errors`` on their own anymore but create an error with ``g_error_new`` and
+  return that.
+
+
+Version 0.1.1
+=============
+
+- Ticket #55: tests/test-channel blocks indefinitely
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c50d260
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+## What is the UFO framework?
+
+UFO is a multi-threaded, GPU-enabled and distributed data processing
+*framework*. It provides base classes, scheduler implementations and run-time
+management to describe a workflow as a graph of processing nodes. The nodes are
+implemented as plugins in the
+[ufo-filters](https://github.com/ufo-kit/ufo-filters) sister project.
+
+
+## Further information
+
+More information can be found at various locations:
+
+* [User manual](http://ufo.kit.edu/extra/manual/html/)
+* [API reference](http://ufo.kit.edu/extra/reference/)
+* [Task reference](http://ufo.kit.edu/extra/filters/reference/)
+
+
+## License
+
+Both ufo-core and ufo-filters are licensed under LGPL 3.
diff --git a/common/cmake/FindOpenCL.cmake b/common/cmake/FindOpenCL.cmake
index f1b1301..222cfcb 100644
--- a/common/cmake/FindOpenCL.cmake
+++ b/common/cmake/FindOpenCL.cmake
@@ -53,6 +53,7 @@ ELSE (APPLE)
               /usr/lib64/nvidia
               /opt/nvidia-current
               /opt/AMDAPP/lib
+              /opt/AMDAPP/lib/x86_64
             )
 
             GET_FILENAME_COMPONENT(OPENCL_LIB_DIR ${OPENCL_LIBRARIES} PATH)
diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt
index 0e9eecc..ab90d6e 100644
--- a/docs/CMakeLists.txt
+++ b/docs/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.6)
 
 find_program(SPHINX sphinx-build PATHS /usr/local/bin /usr/bin)
 mark_as_advanced(SPHINX)
@@ -15,11 +15,7 @@ if(SPHINX)
         list(APPEND sphinx_source "${output_dir}/conf.py")
 
         set(sphinx_static
-            _static/ufo-logo.png
-            _templates/indexcontent.html
-            _templates/indexsidebar.html
-            _templates/layout.html
-            )
+            _static/ufo-logo.png)
 
         configure_file(${input_dir}/conf.py.in ${output_dir}/conf.py)
         
diff --git a/docs/manual/_templates/indexcontent.html b/docs/manual/_templates/indexcontent.html
deleted file mode 100644
index 33883d4..0000000
--- a/docs/manual/_templates/indexcontent.html
+++ /dev/null
@@ -1,48 +0,0 @@
-{% extends "defindex.html" %}
-{% block tables %}
-  <p><strong>Parts of the documentation:</strong></p>
-  <table class="contentstable" align="center"><tr>
-    <td width="50%">
-      <p class="biglink"><a class="biglink" href="{{ pathto("whatsnew/" + version) }}">What's new in UFO {{ version }}?</a><br/>
-          <span class="linkdescr">or <a href="{{ pathto("whatsnew/index") }}">all "What's new" documents</a></span></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("install/index") }}">Installation</a><br/>
-         <span class="linkdescr">install from either packages or source</span></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("using/index") }}">Using UFO</a><br/>
-         <span class="linkdescr">how to use UFO on different platforms</span></p>
-    </td><td width="50%">
-      <p class="biglink"><a class="biglink" href="{{ pathto("json") }}">JSON Configuration</a><br/>
-         <span class="linkdescr">configure UFO graphs textual</span></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("api/index") }}">UFO API</a><br/>
-         <span class="linkdescr">reference for C/C++ programmers</span></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("faq") }}">FAQ</a><br/>
-         <span class="linkdescr">frequently asked questions</span></p>
-    </td></tr>
-  </table>
-
-  <p><strong>Indices and tables:</strong></p>
-  <table class="contentstable" align="center"><tr>
-    <td width="50%">
-      <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">General Index</a><br/>
-         <span class="linkdescr">all functions, classes, terms</span></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("glossary") }}">Glossary</a><br/>
-         <span class="linkdescr">the most important terms explained</span></p>
-    </td><td width="50%">
-      <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">Search page</a><br/>
-         <span class="linkdescr">search this documentation</span></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/>
-         <span class="linkdescr">lists all sections and subsections</span></p>
-    </td></tr>
-  </table>
-
-  <p><strong>Meta information:</strong></p>
-  <table class="contentstable" align="center"><tr>
-    <td width="50%">
-      <p class="biglink"><a class="biglink" href="{{ pathto("todo") }}">TODO</a></p>
-      <p class="biglink"><a class="biglink" href="{{ pathto("bugs") }}">Reporting bugs</a></p>
-    </td><td width="50%">
-      <p class="biglink"><a class="biglink" href="{{ pathto("copyright") }}">Copyright</a></p>
-    </td></tr>
-  </table>
-
-{% endblock %}
-
diff --git a/docs/manual/_templates/indexsidebar.html b/docs/manual/_templates/indexsidebar.html
deleted file mode 100644
index f6b3914..0000000
--- a/docs/manual/_templates/indexsidebar.html
+++ /dev/null
@@ -1,5 +0,0 @@
-    <h3>Other resources</h3>
-    <ul>
-        <li><a href="http://ufo.kit.edu">Project Homepage</a></li>
-        <li><a href="http://ufo.kit.edu/ufo">Development Homepage</a></li>
-    </ul>
diff --git a/docs/manual/_templates/layout.html b/docs/manual/_templates/layout.html
deleted file mode 100644
index 0429147..0000000
--- a/docs/manual/_templates/layout.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends "!layout.html" %}
-{% block rootrellink %}
-    <li><a href="{{ pathto('index') }}">Index</a>  | </li>
-    <li><a href="{{ pathto('contents') }}">Contents</a> »</li>
-{% endblock %}
diff --git a/docs/manual/changelog.rst b/docs/manual/changelog.rst
new file mode 100644
index 0000000..99ab554
--- /dev/null
+++ b/docs/manual/changelog.rst
@@ -0,0 +1,4 @@
+.. _whatsnew-index:
+
+
+.. include:: ../../NEWS
diff --git a/docs/manual/conf.py.in b/docs/manual/conf.py.in
index 6f40fca..8f7dac8 100644
--- a/docs/manual/conf.py.in
+++ b/docs/manual/conf.py.in
@@ -34,7 +34,7 @@ source_suffix = '.rst'
 source_encoding = 'utf-8'
 
 # The master toctree document.
-#master_doc = 'index'
+master_doc = 'index'
 
 # General information about the project.
 project = u'UFO'
@@ -135,15 +135,15 @@ html_static_path = ['_static']
 html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-html_sidebars = {
-    'index' : 'indexsidebar.html'
-}
+# html_sidebars = {
+#     'index' : 'indexsidebar.html'
+# }
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-html_additional_pages = {
-    'index' : 'indexcontent.html'
-}
+# html_additional_pages = {
+#     'index' : 'indexcontent.html'
+# }
 
 # If false, no module index is generated.
 #html_use_modindex = True
diff --git a/docs/manual/contents.rst b/docs/manual/contents.rst
deleted file mode 100644
index 25e0e23..0000000
--- a/docs/manual/contents.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-==========================
-UFO Documentation contents
-==========================
-
-.. toctree::
-
-    whatsnew/index.rst
-    install/index.rst
-    using/index.rst
-    api/index.rst
-    json.rst
-    faq.rst
-    glossary.rst
-
-    bugs.rst
-    copyright.rst
-
diff --git a/docs/manual/index.rst b/docs/manual/index.rst
new file mode 100644
index 0000000..c332563
--- /dev/null
+++ b/docs/manual/index.rst
@@ -0,0 +1,35 @@
+=================
+UFO Documentation
+=================
+
+User documentation
+==================
+
+.. toctree::
+    :maxdepth: 2
+
+    install/index
+    using/index
+    json
+
+
+Developer documentation
+=======================
+
+.. toctree::
+    :maxdepth: 2
+
+    api/index
+    bugs
+    faq
+    changelog
+
+
+Additional notes
+================
+
+.. toctree::
+    :maxdepth: 2
+
+    glossary
+    copyright
diff --git a/docs/manual/using/cluster.rst b/docs/manual/using/cluster.rst
index 66ac63a..7496b25 100644
--- a/docs/manual/using/cluster.rst
+++ b/docs/manual/using/cluster.rst
@@ -21,3 +21,21 @@ In Python this would look like this::
     sched = Ufo.Scheduler(remotes=['tcp://foo.bar.org:5555'])
 
 Address are notated according to `ZeroMQ <http://api.zeromq.org/3-2:zmq-tcp>`_.
+
+
+Streaming vs. replication
+=========================
+
+Work can be executed in two ways: `streaming`, which means data is transferred
+from a master machine to all slaves and returned to the master after computation
+is finished and `replicated` in which each slaves works on its own subset of the
+initial input data. The former must be used if the length of the stream is
+unknown before execution, otherwise the stream could not be split up into equal
+partitions.
+
+Initially, the scheduler is set to streaming mode. To switch to replication
+mode, you have to prepare the scheduler::
+
+    sched = Ufo.Scheduler(remotes=remotes)
+    sched.set_remote_mode(Ufo.RemoteMode.REPLICATE)
+    sched.run(graph)
diff --git a/docs/manual/using/filters.rst b/docs/manual/using/filters.rst
index 57f26b7..a9142ed 100644
--- a/docs/manual/using/filters.rst
+++ b/docs/manual/using/filters.rst
@@ -71,7 +71,7 @@ processing mode your task runs ::
                                     UfoInputParam **in_params,
                                     UfoTaskMode *mode)
     {
-        *mode = UFO_TASK_MODE_SINGLE;
+        *mode = UFO_TASK_MODE_PROCESSOR;
         *n_inputs = 1;
         *in_params = g_new0 (UfoInputParam, 1);
         (*in_params)[0].n_dims = 2;
@@ -80,6 +80,20 @@ processing mode your task runs ::
 This task expects one two-dimensional input. Be aware, that the callee must
 allocate memory for the ``in_params`` data structure.
 
+The mode decides which functions of a task are called. Each task can provide a
+``process`` function that takes input data and optionally writes output data and
+a ``generate`` function that does not take input data but writes data. Both
+functions return a boolean value to signal if data was produced or not (e.g. end
+of stream):
+
+* ``UFO_TASK_MODE_PROCESSOR``: The task reads data and optionally writes data.
+  For that it must implement ``process``.
+* ``UFO_TASK_MODE_GENERATOR``: The task only produces data (e.g. file readers)
+  and must implement ``generate``.
+* ``UFO_TASK_MODE_REDUCTOR``: The tasks reads the input stream and produces
+  another output stream. Reading is accomplished by implementing ``process``
+  whereas production is done by ``generate``.
+
 ``setup`` can be used to initialize data that depends on run-time resources like
 OpenCL contexts etc. This method is called only *once* ::
 
@@ -113,21 +127,26 @@ specify ::
     }
 
 Finally, you have to override the ``process`` method. Note, that the function
-signatures differ for GPU and CPU tasks ::
+signatures is essentially the same for GPU and CPU tasks ::
 
     static gboolean
     ufo_awesome_task_process (UfoGpuTask *task,
                               UfoBuffer **inputs,
                               UfoBuffer *output,
-                              UfoRequisition *requisition,
-                              UfoGpuNode *node)
+                              UfoRequisition *requisition)
     {
-        /* Now we can request cl_mem or float arrays from in and outputs. */
+        UfoGpuNode *node;
         cl_command_queue cmd_queue;
         cl_mem host_in;
         cl_mem host_out;
 
+        /* We have to know to which GPU device we are assigned to */
+        node = UFO_GPU_NODE (ufo_task_node_get_proc_node (UFO_TASK_NODE (task)));
+
+        /* Now, we can get the command queue */
         cmd_queue = ufo_gpu_node_get_cmd_queue (node);
+
+        /* ... and get hold of the data */
         host_in = ufo_buffer_get_device_array (inputs[0], cmd_queue);
         host_out = ufo_buffer_get_device_array (output, cmd_queue);
 
diff --git a/docs/manual/whatsnew/0.1.rst b/docs/manual/whatsnew/0.1.rst
deleted file mode 100644
index 7499916..0000000
--- a/docs/manual/whatsnew/0.1.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-==========================
-What's New in ufo-core 0.1
-==========================
-
-:Author: Matthias Vogelgesang
-
-Changes from version 0.1.0 to 0.1.1
-===================================
-
-Fixed bugs
-----------
-    - Ticket #55: tests/test-channel blocks indefinitely
diff --git a/docs/manual/whatsnew/0.2.rst b/docs/manual/whatsnew/0.2.rst
deleted file mode 100644
index e6dc9a3..0000000
--- a/docs/manual/whatsnew/0.2.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-==========================
-What's New in ufo-core 0.2
-==========================
-
-Major breakage
-==============
-
-- Filters are now prefixed again with ``libfilter`` to allow introspected
-  documentation. Thus, any filter built for 0.1 cannot be used because they are
-  simply not found.
-
-- :c:func:`ufo_plugin_manager_get_filter` received a new third parameter
-  ``error`` that reports errors when opening and loading a UfoFilter from a
-  shared object.
-
-- :c:func:`ufo_resource_manager_add_program` is removed.
-
-- The kernel file name must be passed to :c:func:`ufo_resource_manager_get_kernel`.
-
-- The ``CHECK_ERROR`` macro defined in ``ufo-resource-manager.h`` was renamed to
-  ``CHECK_OPENCL_ERROR`` to better reflect its real purpose.
-
-- The old JSON specification has been changed to reflect the possibilities of
-  the current API. Thus, JSON files that worked under Ufo 0.1 cannot be read
-  with Ufo 0.2.
-
-- Removed the otherwise unused :c:func:`ufo_buffer_get_transfer_time` and
-  replaced this with the more flexible :c:func:`ufo_buffer_get_transfer_timer`.
-
-- Rename :c:func:`ufo_filter_initialize` to
-  :c:func:`ufo_filter_set_plugin_name` that reflects its true meaning.
-
-Scheduling
-----------
-
-A more scheduable way to run filters has been implemented with the virtual
-:c:func:`process_cpu` and :c:func:`process_gpu` methods. Contrary to the old
-way, they act on *one* working set at a time that is passed as an array of
-pointers to :c:type:`UfoBuffer`. Sometimes, a filter needs to setup data
-depending on the input size. For this reason, the virtual method
-:c:func:`initialize` takes a second parameter that is again a list of pointers
-to buffer objects.
-
-Moreover, the :c:type:`UfoScheduler` class has been added that is combining
-the work previously accomplished by :c:func:`ufo_filter_process` and
-:c:func:`ufo_graph_run`. The scheduler orchestrates the filters and
-assigns resources in a meaningful way.
-
-If written in the new kernel style, producer filters must return a boolean flag
-denoting if data was produced or not.
-
-
-General improvements
-====================
-
-- The manual was restructured considerably.
-
-- Saving graphs as JSON files has been added via
-  :c:func:`ufo_graph_save_to_json()`.
-
-- Filters can now wait until their properties satisfy a condition using
-  :c:func:`ufo_filter_wait_until`, see also :ref:`filters-block`.
-
-- A new method :c:func:`ufo_resource_manager_get_kernel_from_source` so that
-  filters can load kernels directly from source.
-
-- Streamlined error handling: Filters should not issue ``g_warnings`` or
-  ``g_errors`` on their own anymore but create an error with ``g_error_new`` and
-  return that.
diff --git a/docs/manual/whatsnew/0.3.rst b/docs/manual/whatsnew/0.3.rst
deleted file mode 100644
index 51f3201..0000000
--- a/docs/manual/whatsnew/0.3.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-==========================
-What's New in ufo-core 0.3
-==========================
-
-Major breakage
-==============
-
-- A graph is now a simple data structure and not a specific graph of task nodes.
-  This is implemented by a TaskGraph.
-
-- Filters are now called TaskNodes and connected with
-  ``ufo_task_graph_connect_nodes`` and ``ufo_task_graph_connect_nodes_full``
-  respectively.
-
-
-Graph expansion
-===============
-
-With 0.2, Using multiple GPUs was possible by manually splitting paths in the
-graph and assigning GPUs. Now, task graphs are automatically expanded depending
-on the number of available GPUs and remote processing slaves that are started
-with the newly added ``ufod`` server software.
-
-
-Minor improvements
-==================
-
-- A ``deploy.sh`` script has been added for easier deployment of the software
-  stack. This is especially useful to install everything in the home directory
-  of the user, who only needs to setup ``LD_LIBRARY_PATH`` and
-  ``GI_TYPELIB_PATH`` correctly to run the software.
diff --git a/docs/manual/whatsnew/index.rst b/docs/manual/whatsnew/index.rst
deleted file mode 100644
index f02388c..0000000
--- a/docs/manual/whatsnew/index.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-.. _whatsnew-index:
-
-======================
-What's New in ufo-core
-======================
-
-The "What's New in ufo-core" series documents the most important changes between
-major versions of the UFO core package.
-
-.. toctree::
-    :maxdepth: 2
-
-    0.3.rst
-    0.2.rst
-    0.1.rst
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index f93a571..bad3bd3 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -9,7 +9,7 @@ if (PYTHON)
     if (WITH_PYTHON_TOOLS)
         set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
         set(SETUP_PY    "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
-        set(DEPS        "${CMAKE_CURRENT_SOURCE_DIR}/ufotools/__init__.py")
+        set(DEPS        "${CMAKE_CURRENT_SOURCE_DIR}/ufonp.c")
         set(OUTPUT      "${CMAKE_CURRENT_BINARY_DIR}/build/timestamp")
 
         configure_file(${SETUP_PY_IN} ${SETUP_PY})
diff --git a/python/setup.py.in b/python/setup.py.in
index 21bd87d..d885308 100644
--- a/python/setup.py.in
+++ b/python/setup.py.in
@@ -1,13 +1,71 @@
+import os
+import subprocess
+from numpy import get_include
 from distutils.core import setup, Extension
 
+
+def parse_pkg_config(pkg_names):
+    """Call `pkg-config` with each package in pkg_name and return a tuple with
+    stripped (include_dirs, library_dirs, libraries, extra_cflags)
+    """
+    include_dirs = []
+    libraries = []
+    lib_dirs = []
+    extra_cflags = []
+
+    for pkg_name in pkg_names:
+        try:
+            proc = subprocess.Popen(['pkg-config', '--libs', pkg_name],
+                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            proc.wait()
+
+            if proc.returncode != 0:
+                raise Exception('package `%s\' not found' % pkg_name)
+
+            r = proc.stdout.read()
+
+            libraries.extend([l.replace('-l', '') for l in r.split() if
+                l.startswith('-l')])
+
+            extra_cflags.extend([l for l in r.split() if not
+                l.startswith('-l')])
+
+            proc = subprocess.Popen(['pkg-config', '--cflags', pkg_name],
+                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            r = proc.stdout.read()
+
+            include_dirs.extend([i.replace('-I', '') for i in r.split() if
+                i.startswith('-I')])
+        except OSError:
+            raise Exception('`pkg-config\' is not installed')
+
+    return (include_dirs, lib_dirs, libraries, extra_cflags)
+
+
 if __name__ == '__main__':
-    setup(name='ufotools',
-          version='${PACKAGE_VERSION}',
-          author='Matthias Vogelgesang',
-          author_email='matthias.vogelgesang at kit.edu',
-          url='http://ufo.kit.edu',
-          license='GPL v3',
-          description='ufo extension module',
-          long_description='ufo extension module',
-          package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' },
-          packages=['ufotools'])
+    packages = ['gobject-2.0', 'pygobject-2.0']
+
+    try:
+        inc_dirs, lib_dirs, libs, extra = parse_pkg_config(packages)
+        inc_dirs.append(get_include())
+        inc_dirs.append('${PROJECT_SOURCE_DIR}')
+        inc_dirs.append('${PROJECT_BINARY_DIR}')
+        libs.append('ufo')
+        lib_dirs.append('${PROJECT_BINARY_DIR}/ufo')
+        extra.append('-std=c99')
+
+        setup(name='ufotools',
+              version='${PACKAGE_VERSION}',
+              author='Matthias Vogelgesang',
+              author_email='matthias.vogelgesang at kit.edu',
+              url='http://ufo.kit.edu',
+              license='GPL v3',
+              description='ufo extension module',
+              long_description='ufo extension module',
+              ext_modules=[Extension('ufonp', ['${CMAKE_CURRENT_SOURCE_DIR}/ufonp.c'],
+                include_dirs=inc_dirs,
+                library_dirs=lib_dirs,
+                libraries=libs,
+                extra_compile_args=extra)])
+    except Exception as e:
+        print 'Configure error:', e
diff --git a/python/ufonp.c b/python/ufonp.c
new file mode 100644
index 0000000..588acab
--- /dev/null
+++ b/python/ufonp.c
@@ -0,0 +1,136 @@
+#include <Python.h>
+#include <pygobject.h>
+#include <numpy/arrayobject.h>
+#include <ufo/ufo.h>
+
+static PyTypeObject *PyGObject_Type = NULL;
+
+static PyObject *
+ufo_asarray(PyObject *self, PyObject *args)
+{
+    PyObject *py_buffer;
+    PyObject *np_array;
+    UfoRequisition req;
+    UfoBuffer *buffer;
+    gfloat *host_array;
+
+    if (!PyArg_ParseTuple(args, "O", &py_buffer))
+        return NULL;
+
+    buffer = UFO_BUFFER (pygobject_get (py_buffer));
+    host_array = ufo_buffer_get_host_array (buffer, NULL);
+    ufo_buffer_get_requisition (buffer, &req);
+
+    npy_intp np_dim_size[req.n_dims];
+
+    for (int i = 0; i < req.n_dims; i++)
+        np_dim_size[i] = req.dims[i];
+
+    np_array = PyArray_NewFromDescr (&PyArray_Type, PyArray_DescrFromType(NPY_FLOAT32),
+                                     req.n_dims, np_dim_size, NULL, host_array, 0, NULL);
+
+    return np_array;
+}
+
+static void
+resize_buffer (UfoBuffer *buffer, guint np_ndims, npy_intp *np_dim_size)
+{
+    UfoRequisition req;
+
+    req.n_dims = np_ndims;
+
+    for (int i = 0; i < np_ndims; i++)
+        req.dims[i] = np_dim_size[i];
+
+    ufo_buffer_resize (buffer, &req);
+}
+
+static PyObject *
+ufo_fromarray (PyObject *self, PyObject *args)
+{
+    PyArrayObject *np_array;
+    guint np_ndims;
+    npy_intp *np_dims;
+    UfoBuffer *buffer;
+    UfoRequisition req;
+    float *host_array;
+
+    if (!PyArg_ParseTuple (args, "O!", &PyArray_Type, &np_array))
+        return NULL;
+
+    np_ndims = PyArray_NDIM (np_array);
+    np_dims = PyArray_DIMS (np_array);
+
+    req.n_dims = np_ndims;
+
+    for (guint i = 0; i < np_ndims; i++)
+        req.dims[i] = np_dims[i];
+
+    buffer = ufo_buffer_new (&req, NULL);
+    host_array = ufo_buffer_get_host_array (buffer, NULL);
+    memcpy (host_array, PyArray_DATA(np_array), ufo_buffer_get_size (buffer));
+
+    return pygobject_new (G_OBJECT (buffer));
+}
+
+static PyObject *
+ufo_fromarray_inplace (PyObject *self, PyObject *args)
+{
+    PyObject *py_buffer;
+    UfoBuffer *buffer;
+    UfoRequisition req;
+    PyArrayObject *np_array;
+    int np_ndims;
+    npy_intp *np_dims;
+    float *host_array;
+
+    if (!PyArg_ParseTuple(args, "OO!", &py_buffer, &PyArray_Type, &np_array))
+        return NULL;
+
+    buffer = UFO_BUFFER (pygobject_get (py_buffer));
+    ufo_buffer_get_requisition (buffer, &req);
+
+    np_ndims = PyArray_NDIM (np_array);
+    np_dims = PyArray_DIMS (np_array);
+
+    if (req.n_dims == np_ndims) {
+        for (int i = 0; i < np_ndims; i++) {
+            if (req.dims[i] != np_dims[i]) {
+                resize_buffer (buffer, req.n_dims, np_dims);
+                break;
+            }
+        }
+    }
+    else {
+        resize_buffer (buffer, np_ndims, np_dims);
+    }
+
+    host_array = ufo_buffer_get_host_array (buffer, NULL);
+    memcpy (host_array, PyArray_DATA (np_array), ufo_buffer_get_size (buffer));
+    return Py_BuildValue("");
+}
+
+static PyMethodDef exported_methods[] = {
+    {"asarray", ufo_asarray, METH_VARARGS, "Convert UfoBuffer to Numpy array"},
+    {"fromarray", ufo_fromarray, METH_VARARGS, "Convert Numpy array to UfoBuffer"},
+    {"fromarray_inplace", ufo_fromarray_inplace, METH_VARARGS, "Convert Numpy array to UfoBuffer in-place"},
+    {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+initufonp(void)
+{
+    PyObject *module = Py_InitModule("ufonp", exported_methods);
+
+    if (module == NULL)
+        return;
+
+    import_array();
+    init_pygobject();
+    module = PyImport_ImportModule("gobject");
+
+    if (module) {
+        PyGObject_Type = (PyTypeObject *) PyObject_GetAttrString(module, "GObject");
+        Py_DECREF(module);
+    }
+}
diff --git a/python/ufotools/__init__.py b/python/ufotools/__init__.py
deleted file mode 100644
index 30ecdf7..0000000
--- a/python/ufotools/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-class CallableFilter(object):
-    def __init__(self, gobject, graph):
-        self.gobject = gobject
-        self.graph = graph
-        self.name = gobject.get_plugin_name()
-
-    def __call__(self, *args, **kwargs):
-        n_inputs = self.gobject.get_num_inputs()
-
-        if n_inputs != len(args):
-            raise TypeError("`%s' receives only %i argument(s) but was called with %i parameters" % (self.name, n_inputs, len(args)))
-
-        for i, target in enumerate(args):
-            self.graph.connect_filters_full(target.gobject, 0, self.gobject, i, Ufo.TransferMode.DISTRIBUTE)
-
-        return self
-
-
-class NodeFactory(object):
-    def __init__(self, plugin_manager, graph):
-        self.pm = plugin_manager
-        self.graph = graph
-
-    def get(self, name, **kwargs):
-        gobject = self.pm.get_filter(name)
-        gobject.set_properties(kwargs)
-        return CallableFilter(gobject, graph)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 84736dd..a9b75cf 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,33 +1,24 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.6)
 
-# configure unit tests
 set(TEST_SRCS
     test-suite.c
-    #test-buffer.c
-    #test-channel.c
+    test-buffer.c
     test-config.c
-    #test-filter.c
-    #test-filter-direct.c
     test-graph.c
     test-profiler.c
+    test-remote-node.c
+    test-mpi-remote-node.c
+    test-zmq-messenger.c
     )
 
 set(SUITE_BIN "test-suite")
 
-option(WITH_TESTS "Build test suite" ON)
+add_executable(${SUITE_BIN} ${TEST_SRCS})
 
-if (WITH_TESTS)
-    include_directories(
-        {CMAKE_CURRENT_SOURCE_DIR})
+target_link_libraries(${SUITE_BIN} ufo ${UFOCORE_DEPS})
 
-    add_executable(${SUITE_BIN} ${TEST_SRCS})
+add_test(${SUITE_BIN} ${SUITE_BIN})
 
-    target_link_libraries(${SUITE_BIN}
-        ufo
-        ${UFOCORE_DEPS})
-
-    add_test(${SUITE_BIN} ${SUITE_BIN})
-
-    configure_file("${CMAKE_CURRENT_SOURCE_DIR}/gtester.xsl"
-        "${CMAKE_CURRENT_BINARY_DIR}/gtester.xsl" @ONLY IMMEDIATE)
-endif()
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/gtester.xsl"
+               "${CMAKE_CURRENT_BINARY_DIR}/gtester.xsl"
+               @ONLY IMMEDIATE)
diff --git a/tests/test-buffer.c b/tests/test-buffer.c
new file mode 100644
index 0000000..89bf503
--- /dev/null
+++ b/tests/test-buffer.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <ufo.h>
+#include "test-suite.h"
+
+typedef struct {
+    UfoBuffer *buffer;
+    guint n_data;
+    const guint8 *data8;
+    const guint16 *data16;
+} Fixture;
+
+static void
+setup (Fixture *fixture, gconstpointer data)
+{
+    static const guint8 data8[8] = { 1, 2, 1, 3, 1, 255, 1, 254 };
+    static const guint16 data16[8] = { 1, 2, 1, 3, 1, 65535, 1, 65534 };
+
+    UfoRequisition requisition = {
+        .n_dims = 1,
+        .dims[0] = 8,
+    };
+
+    fixture->buffer = ufo_buffer_new (&requisition, NULL);
+    fixture->data8 = data8;
+    fixture->data16 = data16;
+    fixture->n_data = 8;
+}
+
+static void
+teardown (Fixture *fixture, gconstpointer data)
+{
+    g_object_unref (fixture->buffer);
+}
+
+static void
+test_convert_8 (Fixture *fixture,
+                gconstpointer unused)
+{
+    gfloat *host_data;
+
+    host_data = ufo_buffer_get_host_array (fixture->buffer, NULL);
+    g_memmove (host_data, fixture->data8, fixture->n_data);
+
+    ufo_buffer_convert (fixture->buffer, UFO_BUFFER_DEPTH_8U);
+    host_data = ufo_buffer_get_host_array (fixture->buffer, NULL);
+
+    for (guint i = 0; i < fixture->n_data; i++)
+        g_assert (host_data[i] == ((gfloat) fixture->data8[i]));
+}
+
+static void
+test_convert_8_from_data (Fixture *fixture,
+                          gconstpointer unused)
+{
+    gfloat *host_data;
+
+    ufo_buffer_convert_from_data (fixture->buffer, fixture->data8, UFO_BUFFER_DEPTH_8U);
+    host_data = ufo_buffer_get_host_array (fixture->buffer, NULL);
+
+    for (guint i = 0; i < fixture->n_data; i++)
+        g_assert (host_data[i] == ((gfloat) fixture->data8[i]));
+}
+
+static void
+test_convert_16 (Fixture *fixture,
+                 gconstpointer unused)
+{
+    gfloat *host_data;
+
+    host_data = ufo_buffer_get_host_array (fixture->buffer, NULL);
+    g_memmove (host_data, fixture->data16, fixture->n_data * sizeof (guint16));
+
+    ufo_buffer_convert (fixture->buffer, UFO_BUFFER_DEPTH_16U);
+    host_data = ufo_buffer_get_host_array (fixture->buffer, NULL);
+
+    for (guint i = 0; i < fixture->n_data; i++)
+        g_assert (host_data[i] == ((gfloat) fixture->data16[i]));
+}
+
+static void
+test_convert_16_from_data (Fixture *fixture,
+                           gconstpointer unused)
+{
+    gfloat *host_data;
+
+    ufo_buffer_convert_from_data (fixture->buffer, fixture->data16, UFO_BUFFER_DEPTH_16U);
+    host_data = ufo_buffer_get_host_array (fixture->buffer, NULL);
+
+    for (guint i = 0; i < fixture->n_data; i++)
+        g_assert (host_data[i] == ((gfloat) fixture->data16[i]));
+}
+
+void
+test_add_buffer (void)
+{
+    g_test_add ("/buffer/convert/8/host",
+                Fixture, NULL,
+                setup, test_convert_8, teardown);
+
+    g_test_add ("/buffer/convert/8/data",
+                Fixture, NULL,
+                setup, test_convert_8_from_data, teardown);
+
+    g_test_add ("/buffer/convert/16/host",
+                Fixture, NULL,
+                setup, test_convert_16, teardown);
+
+    g_test_add ("/buffer/convert/16/data",
+                Fixture, NULL,
+                setup, test_convert_16_from_data, teardown);
+}
diff --git a/tests/test-config.c b/tests/test-config.c
index 1f479e8..3000091 100644
--- a/tests/test-config.c
+++ b/tests/test-config.c
@@ -20,6 +20,27 @@
 #include <ufo.h>
 #include "test-suite.h"
 
+static gboolean
+path_in_array (const gchar *path,
+               GValueArray *array)
+{
+    gboolean found = FALSE;
+
+    for (guint i = 0; i < array->n_values; i++) {
+        if (!g_strcmp0 (path, g_value_get_string (g_value_array_get_nth (array, i))))
+            found = TRUE;
+    }
+
+    return found;
+}
+
+static gboolean
+path_in_list (const gchar *path,
+              GList *list)
+{
+    return g_list_find_custom (list, path, (GCompareFunc) g_strcmp0) != NULL;
+}
+
 static void
 test_path (void)
 {
@@ -50,11 +71,9 @@ test_path (void)
                   "paths", &array,
                   NULL);
 
-    g_assert (array->n_values == 2);
-
     /* Check that paths match the ones we added */
-    g_assert (g_strcmp0 (p1, g_value_get_string (g_value_array_get_nth (array, 0))) == 0);
-    g_assert (g_strcmp0 (p2, g_value_get_string (g_value_array_get_nth (array, 1))) == 0);
+    g_assert (path_in_array (p1, array));
+    g_assert (path_in_array (p2, array));
 
     g_value_array_free (array);
 
@@ -64,8 +83,8 @@ test_path (void)
 
     g_assert (paths != NULL);
     g_assert (g_list_length (paths) >= 2);
-    g_assert (g_strcmp0 (p1, g_list_nth_data (paths, 0)) == 0);
-    g_assert (g_strcmp0 (p2, g_list_nth_data (paths, 1)) == 0);
+    g_assert (path_in_list (p1, paths));
+    g_assert (path_in_list (p2, paths));
 
     g_list_free (paths);
     g_object_unref (object);
diff --git a/tests/test-graph.c b/tests/test-graph.c
index e9759ec..9cbfad3 100644
--- a/tests/test-graph.c
+++ b/tests/test-graph.c
@@ -23,11 +23,18 @@
 typedef struct {
     UfoGraph *graph;
     UfoGraph *sequence;
+    UfoGraph *diamond;
     UfoNode *root;
     UfoNode *target1;
     UfoNode *target2;
+    UfoNode *target3;
 } Fixture;
 
+typedef struct {
+    const gchar *path;
+    void (*test_func) (Fixture *, gconstpointer);
+} TestCase;
+
 static gpointer FOO_LABEL = GINT_TO_POINTER (0xDEADF00D);
 static gpointer BAR_LABEL = GINT_TO_POINTER (0xF00BA);
 static gpointer BAZ_LABEL = GINT_TO_POINTER (0xBA22BA22);
@@ -39,13 +46,15 @@ fixture_setup (Fixture *fixture, gconstpointer data)
     g_assert (UFO_IS_GRAPH (fixture->graph));
 
     fixture->sequence = ufo_graph_new ();
+    g_assert (UFO_IS_GRAPH (fixture->sequence));
 
-    ufo_graph_register_node_type (fixture->graph, UFO_TYPE_NODE);
-    ufo_graph_register_node_type (fixture->sequence, UFO_TYPE_NODE);
+    fixture->diamond = ufo_graph_new ();
+    g_assert (UFO_IS_GRAPH (fixture->diamond));
 
     fixture->root = ufo_node_new (FOO_LABEL);
     fixture->target1 = ufo_node_new (BAR_LABEL);
     fixture->target2 = ufo_node_new (BAZ_LABEL);
+    fixture->target3 = ufo_node_new (FOO_LABEL);
 
     ufo_graph_connect_nodes (fixture->graph,
                              fixture->root,
@@ -66,6 +75,26 @@ fixture_setup (Fixture *fixture, gconstpointer data)
                              fixture->target1,
                              fixture->target2,
                              FOO_LABEL);
+
+    ufo_graph_connect_nodes (fixture->diamond,
+                             fixture->root,
+                             fixture->target1,
+                             BAR_LABEL);
+
+    ufo_graph_connect_nodes (fixture->diamond,
+                             fixture->root,
+                             fixture->target2,
+                             BAR_LABEL);
+
+    ufo_graph_connect_nodes (fixture->diamond,
+                             fixture->target1,
+                             fixture->target3,
+                             BAR_LABEL);
+
+    ufo_graph_connect_nodes (fixture->diamond,
+                             fixture->target2,
+                             fixture->target3,
+                             BAR_LABEL);
 }
 
 static void
@@ -73,6 +102,10 @@ fixture_teardown (Fixture *fixture, gconstpointer data)
 {
     g_object_unref (fixture->graph);
     g_object_unref (fixture->sequence);
+    g_object_unref (fixture->diamond);
+    g_object_unref (fixture->target1);
+    g_object_unref (fixture->target2);
+    g_object_unref (fixture->target3);
 }
 
 static void
@@ -124,6 +157,20 @@ test_get_num_edges (Fixture *fixture, gconstpointer data)
 }
 
 static void
+test_get_num_successors (Fixture *fixture, gconstpointer data)
+{
+    g_assert (ufo_graph_get_num_successors (fixture->sequence, fixture->root) == 1);
+    g_assert (ufo_graph_get_num_successors (fixture->diamond, fixture->root) == 2);
+}
+
+static void
+test_get_num_predecessors (Fixture *fixture, gconstpointer data)
+{
+    g_assert (ufo_graph_get_num_predecessors (fixture->sequence, fixture->target1) == 1);
+    g_assert (ufo_graph_get_num_predecessors (fixture->diamond, fixture->target3) == 2);
+}
+
+static void
 test_get_edges (Fixture *fixture, gconstpointer data)
 {
     GList *edges;
@@ -193,6 +240,8 @@ test_expansion (Fixture *fixture, gconstpointer data)
     GList *successors;
     UfoNode *node;
     GList *path = NULL;
+    guint index;
+    guint other_index;
 
     path = g_list_append (path, fixture->root);
     path = g_list_append (path, fixture->target1);
@@ -205,6 +254,16 @@ test_expansion (Fixture *fixture, gconstpointer data)
     g_assert (g_list_length (successors) == 2);
 
     node = UFO_NODE (g_list_nth_data (successors, 0));
+    index = ufo_node_get_index (node);
+
+    g_assert ((index == 0) || (index == 1));
+    g_assert (ufo_node_get_total (node) == 2);
+
+    node = UFO_NODE (g_list_nth_data (successors, 1));
+    other_index = 1 - index;
+    g_assert (ufo_node_get_index (node) == other_index);
+    g_assert (ufo_node_get_total (node) == 2);
+
     g_list_free (successors);
 
     successors = ufo_graph_get_successors (fixture->sequence, node);
@@ -215,6 +274,48 @@ test_expansion (Fixture *fixture, gconstpointer data)
     g_assert (ufo_node_equal (node, fixture->target2));
 }
 
+static void
+test_copy (Fixture *fixture, gconstpointer data)
+{
+    UfoGraph *copy;
+    GList *roots;
+    GList *successors;
+    GError *error = NULL;
+
+    copy = ufo_graph_copy (fixture->graph, &error);
+    g_assert (copy != NULL);
+    g_assert_no_error (error);
+    g_assert (ufo_graph_get_num_edges (copy) == 2);
+    g_assert (ufo_graph_get_num_nodes (copy) == 3);
+
+    /* Check that copying preserved the order */
+    roots = ufo_graph_get_roots (copy);
+    g_assert (ufo_node_get_label (g_list_nth_data (roots, 0)) == FOO_LABEL);
+
+    successors = ufo_graph_get_successors (copy,
+                                           g_list_nth_data (roots, 0));
+
+    g_assert (ufo_node_get_label (g_list_nth_data (successors, 0)) == BAR_LABEL);
+    g_assert (ufo_node_get_label (g_list_nth_data (successors, 1)) == BAZ_LABEL);
+    g_list_free (successors);
+    g_list_free (roots);
+    g_object_unref (copy);
+
+    copy = ufo_graph_copy (fixture->sequence, &error);
+    g_assert (copy != NULL);
+    g_assert_no_error (error);
+    g_assert (ufo_graph_get_num_edges (copy) == 2);
+    g_assert (ufo_graph_get_num_nodes (copy) == 3);
+    g_object_unref (copy);
+
+    copy = ufo_graph_copy (fixture->diamond, &error);
+    g_assert (copy != NULL);
+    g_assert_no_error (error);
+    g_assert (ufo_graph_get_num_edges (copy) == 4);
+    g_assert (ufo_graph_get_num_nodes (copy) == 4);
+    g_object_unref (copy);
+}
+
 static gboolean
 always_true (UfoNode *node, gpointer user_data)
 {
@@ -234,50 +335,60 @@ test_get_nodes_filtered (Fixture *fixture, gconstpointer data)
     g_list_free (nodes);
 }
 
-void
-test_add_graph (void)
+static void
+test_flatten (Fixture *fixture, gconstpointer data)
 {
-    g_test_add ("/graph/connected",
-                Fixture, NULL,
-                fixture_setup, test_connected, fixture_teardown);
-
-    g_test_add ("/graph/nodes/number",
-                Fixture, NULL,
-                fixture_setup, test_get_num_nodes, fixture_teardown);
-
-    g_test_add ("/graph/nodes/roots",
-                Fixture, NULL,
-                fixture_setup, test_get_roots, fixture_teardown);
-
-    g_test_add ("/graph/nodes/successors",
-                Fixture, NULL,
-                fixture_setup, test_get_successors, fixture_teardown);
-
-    g_test_add ("/graph/nodes/predecessors",
-                Fixture, NULL,
-                fixture_setup, test_get_predecessors, fixture_teardown);
+    GList *levels;
+    GList *roots;
+    GList *second_level;
+    GList *third_level;
 
-    g_test_add ("/graph/nodes/filtered",
-                Fixture, NULL,
-                fixture_setup, test_get_nodes_filtered, fixture_teardown);
+    levels = ufo_graph_flatten (fixture->diamond);
+    g_assert (g_list_length (levels) == 3);
 
-    g_test_add ("/graph/edges/number",
-                Fixture, NULL,
-                fixture_setup, test_get_num_edges, fixture_teardown);
+    roots = g_list_nth_data (levels, 0);
+    g_assert (g_list_length (roots) == 1);
+    g_assert (g_list_nth_data (roots, 0) == fixture->root);
+    g_list_free (roots);
 
-    g_test_add ("/graph/edges/all",
-                Fixture, NULL,
-                fixture_setup, test_get_edges, fixture_teardown);
+    second_level = g_list_nth_data (levels, 1);
+    g_assert (g_list_length (second_level) == 2);
+    g_assert (g_list_find (second_level, fixture->target1) != NULL);
+    g_assert (g_list_find (second_level, fixture->target2) != NULL);
+    g_list_free (second_level);
 
-    g_test_add ("/graph/edges/remove",
-                Fixture, NULL,
-                fixture_setup, test_remove_edge, fixture_teardown);
+    third_level = g_list_nth_data (levels, 2);
+    g_assert (g_list_length (third_level) == 1);
+    g_assert (g_list_find (third_level, fixture->target3) != NULL);
+    g_list_free (third_level);
 
-    g_test_add ("/graph/labels",
-                Fixture, NULL,
-                fixture_setup, test_get_labels, fixture_teardown);
+    g_list_free (levels);
+}
 
-    g_test_add ("/graph/expansion",
-                Fixture, NULL,
-                fixture_setup, test_expansion, fixture_teardown);
+void
+test_add_graph (void)
+{
+    TestCase test_cases[] = {
+        { "/graph/connected",               test_connected },
+        { "/graph/nodes/number",            test_get_num_nodes },
+        { "/graph/nodes/roots",             test_get_roots },
+        { "/graph/nodes/successors",        test_get_successors },
+        { "/graph/nodes/successors/num",    test_get_num_successors },
+        { "/graph/nodes/predecessors",      test_get_predecessors },
+        { "/graph/nodes/predecessors/num",  test_get_num_predecessors },
+        { "/graph/nodes/filtered",          test_get_nodes_filtered },
+        { "/graph/edges/number",            test_get_num_edges },
+        { "/graph/edges/all",               test_get_edges },
+        { "/graph/edges/remove",            test_remove_edge },
+        { "/graph/labels",                  test_get_labels },
+        { "/graph/expansion",               test_expansion },
+        { "/graph/copy",                    test_copy },
+        { "/graph/flatten",                 test_flatten },
+        { NULL, NULL }
+    };
+
+    for (guint i = 0; test_cases[i].path != NULL; i++) {
+        g_test_add (test_cases[i].path, Fixture, NULL,
+                    fixture_setup, test_cases[i].test_func, fixture_teardown);
+    }
 }
diff --git a/tests/test-mpi-remote-node.c b/tests/test-mpi-remote-node.c
new file mode 100644
index 0000000..fc55c71
--- /dev/null
+++ b/tests/test-mpi-remote-node.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* needed to avoid emty translation unit warning */
+void __unused_test_mpi_remote_fn_(void);
+
+#ifdef MPI
+
+#include <string.h>
+#include <ufo.h>
+#include "test-suite.h"
+#include <mpi.h>
+#include <unistd.h>
+
+typedef struct {
+    UfoDaemon *daemon;
+    UfoConfig *config;
+    UfoRemoteNode **remote_nodes;
+    gint global_size;
+    gint rank;
+} Fixture;
+
+static void
+setup (Fixture *fixture, gconstpointer data)
+{
+    fixture->config = ufo_config_new ();
+    int size, rank;
+
+    MPI_Comm_rank (MPI_COMM_WORLD, &rank);
+    MPI_Comm_size (MPI_COMM_WORLD, &size);
+    fixture->global_size = size;
+    fixture->rank = rank;
+
+    if (fixture->rank == 0) {
+        g_message ("Number of mpi processes: %d", fixture->global_size);
+        g_message ("Number of remote_nodes: %d", fixture->global_size - 1);
+        fixture->remote_nodes = g_malloc0 (fixture->global_size - 1);
+        // create remote nodes
+        for (int i = 0; i < fixture->global_size - 1; i++) {
+            gchar *addr = g_strdup_printf("%d", i+1);
+            fixture->remote_nodes[i] = (UfoRemoteNode *) ufo_remote_node_new (addr);
+        }
+    } else {
+        gchar *addr = g_strdup_printf("%d", rank);
+        fixture->daemon = ufo_daemon_new (fixture->config, addr);
+        ufo_daemon_start (fixture->daemon);
+    }
+}
+
+static void
+teardown (Fixture *fixture, gconstpointer data)
+{
+    g_message ("teardown");
+    if (fixture->rank == 0) {
+        for (int i = 1; i <= fixture->global_size - 1; i++) {
+            UfoRemoteNode *node = fixture->remote_nodes[i-1];
+            ufo_remote_node_terminate (node);
+            g_object_unref (node);
+            g_message ("teardown node %d done", i-1);
+        }
+    } else {
+        ufo_daemon_wait_finish (fixture->daemon);
+        g_object_unref (fixture->daemon);
+        g_message ("teardown done");
+    }
+}
+
+static void
+test_remote_node_get_num_cpus (Fixture *fixture,
+                               gconstpointer unused)
+{
+    if (fixture->rank == 0) {
+        for (int i = 1; i <= fixture->global_size - 1; i++) {
+            guint n_gpus = ufo_remote_node_get_num_gpus (fixture->remote_nodes[i-1]);
+            g_message ("Found %d number of GPUs at remotenode %d", n_gpus, i);
+            g_assert (n_gpus > 0);
+        }
+    }
+}
+
+static void
+test_remote_node_get_structure (Fixture *fixture,
+                               gconstpointer unused)
+{
+    if (fixture->rank == 0) {
+        for (int i = 1; i <= fixture->global_size - 1 ; i++) {
+            UfoTaskMode mode;
+            UfoInputParam *in_params;
+            guint n_inputs;
+            ufo_remote_node_get_structure (fixture->remote_nodes[i-1], &n_inputs, &in_params, &mode);
+            g_message ("received n_inputs == %d from remote node %d", n_inputs, i);
+            g_assert (n_inputs == 1);
+            g_message ("received n_dims == %d from remote node %d", in_params->n_dims, i);
+            g_assert (in_params->n_dims == 2);
+        }
+    }
+}
+
+void
+test_add_mpi_remote_node (void)
+{
+    g_test_add ("/remotenode/get_structure",
+                Fixture, NULL,
+                setup, test_remote_node_get_structure, teardown);
+    g_test_add ("/remotenode/get_num_cpus",
+                Fixture, NULL,
+                setup, test_remote_node_get_num_cpus, teardown);
+}
+
+#endif
\ No newline at end of file
diff --git a/tests/test-profiler.c b/tests/test-profiler.c
index ce9b4ae..a0ab79d 100644
--- a/tests/test-profiler.c
+++ b/tests/test-profiler.c
@@ -32,8 +32,7 @@ typedef struct {
 static void
 fixture_setup (Fixture *fixture, gconstpointer data)
 {
-    fixture->profiler = ufo_profiler_new (UFO_PROFILER_LEVEL_OPENCL |
-                                          UFO_PROFILER_LEVEL_IO);
+    fixture->profiler = ufo_profiler_new ();
     g_assert (UFO_IS_PROFILER (fixture->profiler));
 }
 
diff --git a/tests/test-remote-node.c b/tests/test-remote-node.c
new file mode 100644
index 0000000..8642988
--- /dev/null
+++ b/tests/test-remote-node.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <ufo.h>
+#include "test-suite.h"
+
+typedef struct {
+    UfoDaemon *daemon;
+    UfoConfig *config;
+    UfoRemoteNode *remote_node;
+} Fixture;
+
+static void
+setup (Fixture *fixture, gconstpointer data)
+{
+    gchar *addr = g_strdup ("tcp://127.0.0.1:5555");
+    fixture->config = ufo_config_new ();
+
+    fixture->daemon = ufo_daemon_new (fixture->config, addr);
+    ufo_daemon_start (fixture->daemon);
+
+    fixture->remote_node = (UfoRemoteNode *) ufo_remote_node_new (addr);
+}
+
+static void
+teardown (Fixture *fixture, gconstpointer data)
+{
+    g_object_unref (fixture->remote_node);
+    ufo_daemon_stop (fixture->daemon);
+
+    g_object_unref (fixture->daemon);
+}
+
+static void
+test_remote_node_get_num_cpus (Fixture *fixture,
+                               gconstpointer unused)
+{
+    guint n_gpus = ufo_remote_node_get_num_gpus (fixture->remote_node);
+    g_debug ("Found %d number of GPUs at remotenode", n_gpus);
+    g_assert (n_gpus > 0);
+}
+
+static void
+test_remote_node_get_structure (Fixture *fixture,
+                               gconstpointer unused)
+{
+    UfoTaskMode mode;
+    UfoInputParam *in_params;
+    guint n_inputs;
+    ufo_remote_node_get_structure (fixture->remote_node, &n_inputs, &in_params, &mode);
+    g_message ("received n_inputs == %d", n_inputs);
+    g_assert (n_inputs == 1);
+    g_message ("received n_dims == %d", in_params->n_dims);
+    g_assert (in_params->n_dims == 2);
+
+}
+
+void
+test_add_remote_node (void)
+{
+    g_test_add ("/remotenode/get_structure",
+                Fixture, NULL,
+                setup, test_remote_node_get_structure, teardown);
+    g_test_add ("/remotenode/get_num_cpus",
+                Fixture, NULL,
+                setup, test_remote_node_get_num_cpus, teardown);
+}
diff --git a/tests/test-suite.c b/tests/test-suite.c
index d16b214..a649369 100644
--- a/tests/test-suite.c
+++ b/tests/test-suite.c
@@ -19,6 +19,9 @@
 
 #include <glib-object.h>
 #include "test-suite.h"
+#ifdef MPI
+#include <mpi.h>
+#endif
 
 static void
 ignore_log (const gchar     *domain,
@@ -26,6 +29,7 @@ ignore_log (const gchar     *domain,
             const gchar     *message,
             gpointer         data)
 {
+    g_print ("%s\n",message);
 }
 
 int main(int argc, char *argv[])
@@ -34,14 +38,27 @@ int main(int argc, char *argv[])
     g_test_init(&argc, &argv, NULL);
     g_test_bug_base("http://ufo.kit.edu/ufo/ticket");
 
+    // g_log_set_handler ("Ufo", G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO, ignore_log, NULL);
     g_log_set_handler ("Ufo", G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG, ignore_log, NULL);
     g_log_set_handler ("ocl", G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG, ignore_log, NULL);
 
+    test_add_buffer ();
     test_add_config ();
     test_add_graph ();
     test_add_profiler ();
-
+#ifdef MPI
+    int provided;
+    MPI_Init_thread (&argc, &argv, MPI_THREAD_MULTIPLE, &provided);
+    test_add_mpi_remote_node ();
+#else
+    test_add_zmq_messenger ();
+    test_add_remote_node ();
+#endif
     g_test_run();
 
+#ifdef MPI
+    MPI_Finalize ();
+#endif
+
     return 0;
 }
diff --git a/tests/test-suite.h b/tests/test-suite.h
index 02bf8ff..bae1d66 100644
--- a/tests/test-suite.h
+++ b/tests/test-suite.h
@@ -1,8 +1,12 @@
 #ifndef TEST_SUITE_H
 #define TEST_SUITE_H
 
+void test_add_buffer (void);
 void test_add_config (void);
 void test_add_graph (void);
 void test_add_profiler (void);
+void test_add_remote_node (void);
+void test_add_mpi_remote_node (void);
+void test_add_zmq_messenger (void);
 
 #endif
diff --git a/tests/test-zmq-messenger.c b/tests/test-zmq-messenger.c
new file mode 100644
index 0000000..76fc96c
--- /dev/null
+++ b/tests/test-zmq-messenger.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ufo/ufo.h>
+#include <ufo/ufo-zmq-messenger.h>
+#include "test-suite.h"
+
+typedef struct {
+    gchar *addr;
+} Fixture;
+
+static void
+setup (Fixture *fixture, gconstpointer data)
+{
+    fixture->addr = g_strdup ("tcp://127.0.0.1:5555");
+}
+
+static void
+teardown (Fixture *fixture, gconstpointer data)
+{
+    g_free (fixture->addr);
+}
+
+static void send_num_devices_request (gpointer unused)
+{
+    UfoMessenger *msger = UFO_MESSENGER (ufo_zmq_messenger_new ());
+    gchar *addr = g_strdup ("tcp://127.0.0.1:5555");
+    ufo_messenger_connect (msger, addr, UFO_MESSENGER_CLIENT);
+
+    guint x = 0;
+    while (x++ < 10) {
+        UfoMessage *request = ufo_message_new (UFO_MESSAGE_GET_NUM_DEVICES, 0);
+        UfoMessage *response;
+
+        response = ufo_messenger_send_blocking (msger, request, NULL);
+
+        guint16 num_devices = *(guint16 *) response->data;
+        g_assert (num_devices == x);
+
+        ufo_message_free (request);
+        ufo_message_free (response);
+    }
+    ufo_zmq_messenger_disconnect (msger);
+    g_object_unref (msger);
+}
+
+static void handle_num_devices (gpointer unused)
+{
+    UfoMessenger *msger = UFO_MESSENGER (ufo_zmq_messenger_new ());
+    gchar *addr = g_strdup ("tcp://127.0.0.1:5555");
+    ufo_messenger_connect (msger, addr, UFO_MESSENGER_SERVER);
+
+    guint16 x = 0;
+    GError *err = NULL;
+    while (x++ < 10) {
+        UfoMessage *msg = ufo_messenger_recv_blocking (UFO_MESSENGER (msger), &err);
+        if (err != NULL)
+            g_critical ("%s", err->message);
+
+        UfoMessage *resp;
+        switch (msg->type) {
+            case UFO_MESSAGE_GET_NUM_DEVICES:
+                resp = ufo_message_new (UFO_MESSAGE_ACK, sizeof (guint16));
+                *(guint16 *)resp->data = x;
+                ufo_zmq_messenger_send_blocking (msger, resp, NULL);
+                ufo_message_free (resp);
+                break;
+            default:
+                g_critical ("Unexpected message type: %d", msg->type);
+            break;
+        }
+        ufo_message_free (msg);
+    };
+
+    ufo_zmq_messenger_disconnect (msger);
+    g_object_unref (msger);
+}
+
+static void test_zmq_messenger (Fixture *fixture, gconstpointer unused)
+{
+    GThread *server = g_thread_create ((GThreadFunc) handle_num_devices, NULL, TRUE, NULL);
+    GThread *client = g_thread_create ((GThreadFunc) send_num_devices_request, NULL, TRUE, NULL);
+
+    g_thread_join (client);
+    g_thread_join (server);
+}
+
+
+void
+test_add_zmq_messenger (void)
+{
+    g_test_add ("/zmq_messenger/test_messenger",
+                Fixture, NULL,
+                setup, test_zmq_messenger, teardown);
+}
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 48af150..f64f29f 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.6)
 
 add_executable(runjson runjson.c)
 add_executable(ufod ufod.c)
@@ -7,7 +7,4 @@ target_link_libraries(runjson ufo ${UFOCORE_DEPS})
 target_link_libraries(ufod ufo ${UFOCORE_DEPS})
 
 install(TARGETS ufod runjson
-        RUNTIME DESTINATION bin)
-
-install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/clprof
-        DESTINATION bin)
+        RUNTIME DESTINATION ${UFO_BINDIR})
diff --git a/tools/runjson.c b/tools/runjson.c
index e108437..f121688 100644
--- a/tools/runjson.c
+++ b/tools/runjson.c
@@ -21,6 +21,12 @@
 #include <stdlib.h>
 #include <ufo/ufo.h>
 
+#ifdef MPI
+#include <mpi.h>
+#include <ufo/ufo-mpi-messenger.h>
+#include <unistd.h>
+#endif
+
 static void
 handle_error (const gchar *prefix, GError *error, UfoGraph *graph)
 {
@@ -45,25 +51,31 @@ string_array_to_list (gchar **array)
     return result;
 }
 
+static UfoConfig *
+get_config (gchar **paths)
+{
+    GList *path_list = NULL;
+    UfoConfig *config;
+
+    config = ufo_config_new ();
+    path_list = string_array_to_list (paths);
+    ufo_config_add_paths (config, path_list);
+
+    g_list_free (path_list);
+    return config;
+}
+
 static void
 execute_json (const gchar *filename,
-              gchar **paths,
+              UfoConfig *config,
               gchar **addresses)
 {
-    UfoConfig       *config;
     UfoTaskGraph    *task_graph;
     UfoScheduler    *scheduler;
     UfoPluginManager *manager;
-    GList *path_list = NULL;
     GList *address_list = NULL;
     GError *error = NULL;
 
-    config = ufo_config_new ();
-
-    path_list = string_array_to_list (paths);
-    ufo_config_add_paths (config, path_list);
-    g_list_free (path_list);
-
     manager = ufo_plugin_manager_new (config);
 
     task_graph = UFO_TASK_GRAPH (ufo_task_graph_new ());
@@ -80,9 +92,76 @@ execute_json (const gchar *filename,
     g_object_unref (task_graph);
     g_object_unref (scheduler);
     g_object_unref (manager);
-    g_object_unref (config);
 }
 
+#ifdef MPI
+
+static void
+mpi_terminate_processes (gint global_size)
+{
+    for (int i = 1; i < global_size; i++) {
+        gchar *addr = g_strdup_printf ("%d", i);
+        UfoMessage *poisonpill = ufo_message_new (UFO_MESSAGE_TERMINATE, 0);
+        UfoMessenger *msger = UFO_MESSENGER (ufo_mpi_messenger_new ());
+        ufo_mpi_messenger_connect (msger, addr, UFO_MESSENGER_CLIENT);
+        g_debug ("sending poisonpill to %s", addr);
+        ufo_messenger_send_blocking (msger, poisonpill, NULL);
+        ufo_message_free (poisonpill);
+        ufo_messenger_disconnect (msger);
+    }
+}
+
+static gchar**
+mpi_build_addresses (gint global_size)
+{
+    /* build addresses by MPI_COMM_WORLD size, exclude rank 0 but
+       have room for NULL termination */
+    gchar **addresses = g_malloc (sizeof (gchar *) * global_size);
+    for (int i = 1; i < global_size; i++) {
+        addresses[i - 1] = g_strdup_printf ("%d", i);
+    }
+    addresses[global_size - 1] = NULL;
+
+    return addresses;
+}
+
+static void
+mpi_init (int *argc, char *argv[], gint *rank, gint *global_size)
+{
+    gint provided;
+    MPI_Init_thread (argc, &argv, MPI_THREAD_MULTIPLE, &provided);
+   
+    MPI_Comm_rank (MPI_COMM_WORLD, rank);
+    MPI_Comm_size (MPI_COMM_WORLD, global_size);
+
+    if (*global_size == 1) {
+        g_critical ("Warning: running MPI instance but found only single process");
+        exit (0);
+    }
+
+#ifdef DEBUG
+    // get us some time to attach a gdb session to the pids
+    g_debug ("Process PID %d ranked %d of %d  - ready for attach\n",
+             getpid(), rank, *global_size - 1);
+
+    sleep (3);
+#endif
+
+}
+
+#endif
+
+#ifdef DEBUG
+static void
+ignore_log (const gchar     *domain,
+            GLogLevelFlags   flags,
+            const gchar     *message,
+            gpointer         data)
+{
+    g_print ("%s\n",message);
+}
+#endif
+
 int main(int argc, char *argv[])
 {
     GOptionContext *context;
@@ -90,12 +169,15 @@ int main(int argc, char *argv[])
     gchar **paths = NULL;
     gchar **addresses = NULL;
     gboolean show_version = FALSE;
+    UfoConfig *config = NULL;
 
     GOptionEntry entries[] = {
         { "path", 'p', 0, G_OPTION_ARG_STRING_ARRAY, &paths,
           "Path to node plugins or OpenCL kernels", NULL },
+#ifndef MPI
         { "address", 'a', 0, G_OPTION_ARG_STRING_ARRAY, &addresses,
           "Address of remote server running `ufod'", NULL },
+#endif
         { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version,
           "Show version information", NULL },
         { NULL }
@@ -103,6 +185,10 @@ int main(int argc, char *argv[])
 
     g_type_init();
 
+#ifdef DEBUG
+    g_log_set_handler ("Ufo", G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG, ignore_log, NULL);
+#endif
+
     context = g_option_context_new ("FILE");
     g_option_context_add_main_entries (context, entries, NULL);
 
@@ -125,11 +211,37 @@ int main(int argc, char *argv[])
         return 1;
     }
 
-    execute_json (argv[argc-1], paths, addresses);
+    config = get_config (paths);
+
+#ifdef MPI
+    gint rank, size;
+    mpi_init (&argc, argv, &rank, &size);
+
+    if (rank == 0) {
+        addresses = mpi_build_addresses (size);
+    } else {
+        gchar *addr = g_strdup_printf("%d", rank);
+        UfoDaemon *daemon = ufo_daemon_new (config, addr);
+        ufo_daemon_start (daemon);
+        ufo_daemon_wait_finish (daemon);
+        MPI_Finalize ();
+        exit(EXIT_SUCCESS);
+    }
+#endif
+
+    execute_json (argv[argc-1], config, addresses);
+
+#ifdef MPI
+    if (rank == 0) {
+        mpi_terminate_processes (size);
+        MPI_Finalize ();
+    }
+#endif
 
     g_strfreev (paths);
     g_strfreev (addresses);
     g_option_context_free (context);
+    g_object_unref (config);
 
     return 0;
 }
diff --git a/tools/ufod.c b/tools/ufod.c
index 6caabae..d7f29b7 100644
--- a/tools/ufod.c
+++ b/tools/ufod.c
@@ -24,293 +24,18 @@
 #else
 #include <CL/cl.h>
 #endif
-#include <zmq.h>
+#include <signal.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ufo/ufo.h>
 
-typedef struct {
-    UfoConfig *config;
-    UfoPluginManager *manager;
-    UfoTaskGraph *task_graph;
-    UfoScheduler *scheduler;
-    gpointer socket;
-    UfoNode *input_task;
-    UfoNode *output_task;
-    UfoBuffer *input;
-} ServerPrivate;
+static UfoDaemon *daemon;
 
 typedef struct {
     gchar **paths;
     gchar *addr;
 } Options;
 
-#define CHECK_ZMQ(r) if (r == -1) g_warning ("%s:%i: zmq_error: %s\n", __FILE__, __LINE__, zmq_strerror (errno));
-
-static gpointer run_scheduler (ServerPrivate *priv);
-
-static void
-ufo_msg_send (UfoMessage *msg, gpointer socket, gint flags)
-{
-    zmq_msg_t reply;
-
-    zmq_msg_init_size (&reply, sizeof (UfoMessage));
-    memcpy (zmq_msg_data (&reply), msg, sizeof (UfoMessage));
-    zmq_msg_send (&reply, socket, flags);
-    zmq_msg_close (&reply);
-}
-
-static void
-send_ack (gpointer socket)
-{
-    UfoMessage msg;
-    msg.type = UFO_MESSAGE_ACK;
-    ufo_msg_send (&msg, socket, 0);
-}
-
-static void
-handle_get_num_devices (ServerPrivate *priv)
-{
-    UfoMessage msg;
-    cl_context context;
-
-    context = ufo_scheduler_get_context (priv->scheduler);
-
-    UFO_RESOURCES_CHECK_CLERR (clGetContextInfo (context,
-                                                 CL_CONTEXT_NUM_DEVICES,
-                                                 sizeof (cl_uint),
-                                                 &msg.d.n_devices,
-                                                 NULL));
-
-    ufo_msg_send (&msg, priv->socket, 0);
-}
-
-static UfoNode *
-remove_dummy_if_present (UfoGraph *graph,
-                         UfoNode *first)
-{
-    UfoNode *real = first;
-
-    if (UFO_IS_DUMMY_TASK (first)) {
-        UfoNode *dummy;
-        GList *successors;
-
-        dummy = first;
-        successors = ufo_graph_get_successors (graph, dummy);
-        g_assert (g_list_length (successors) == 1);
-        real = UFO_NODE (successors->data);
-        g_list_free (successors);
-        ufo_graph_remove_edge (graph, dummy, real);
-    }
-
-    return real;
-}
-
-static void
-handle_json (ServerPrivate *priv)
-{
-    zmq_msg_t json_msg;
-    gsize size;
-    gchar *json;
-    GList *roots;
-    GList *leaves;
-    UfoNode *first;
-    UfoNode *last;
-    GError *error = NULL;
-
-    zmq_msg_init (&json_msg);
-    size = (gsize) zmq_msg_recv (&json_msg, priv->socket, 0);
-
-    json = g_malloc0 (size + 1);
-    memcpy (json, zmq_msg_data (&json_msg), size);
-    zmq_msg_close (&json_msg);
-
-    /* Setup local task graph */
-    priv->task_graph = UFO_TASK_GRAPH (ufo_task_graph_new ());
-    ufo_task_graph_read_from_data (priv->task_graph, priv->manager, json, &error);
-
-    if (error != NULL) {
-        g_printerr ("%s\n", error->message);
-        /* Send error to master */
-        return;
-    }
-
-    roots = ufo_graph_get_roots (UFO_GRAPH (priv->task_graph));
-    g_assert (g_list_length (roots) == 1);
-
-    leaves = ufo_graph_get_leaves (UFO_GRAPH (priv->task_graph));
-    g_assert (g_list_length (leaves) == 1);
-
-    first = UFO_NODE (g_list_nth_data (roots, 0));
-    last = UFO_NODE (g_list_nth_data (leaves, 0));
-
-    first = remove_dummy_if_present (UFO_GRAPH (priv->task_graph), first);
-
-    priv->input_task = ufo_input_task_new ();
-    priv->output_task = ufo_output_task_new (2);
-
-    ufo_graph_connect_nodes (UFO_GRAPH (priv->task_graph),
-                             priv->input_task, first,
-                             GINT_TO_POINTER (0));
-
-    ufo_graph_connect_nodes (UFO_GRAPH (priv->task_graph),
-                             last, priv->output_task,
-                             GINT_TO_POINTER (0));
-
-    g_thread_create ((GThreadFunc) run_scheduler, priv, FALSE, NULL);
-    g_free (json);
-    send_ack (priv->socket);
-}
-
-static void
-handle_setup (ServerPrivate *priv)
-{
-    g_message ("Setup requested");
-    send_ack (priv->socket);
-}
-
-static void
-handle_get_structure (ServerPrivate *priv)
-{
-    UfoMessage header;
-    UfoInputParam in_param;
-    zmq_msg_t data_msg;
-
-    g_message ("Structure requested");
-    header.type = UFO_MESSAGE_STRUCTURE;
-
-    /* TODO: do not hardcode these */
-    header.d.n_inputs = 1;
-    in_param.n_dims = 2;
-
-    zmq_msg_init_size (&data_msg, sizeof (UfoInputParam));
-    memcpy (zmq_msg_data (&data_msg), &in_param, sizeof (UfoInputParam));
-
-    ufo_msg_send (&header, priv->socket, ZMQ_SNDMORE);
-    zmq_msg_send (&data_msg, priv->socket, 0);
-    zmq_msg_close (&data_msg);
-}
-
-static void
-handle_send_inputs (ServerPrivate *priv)
-{
-    UfoRequisition *requisition;
-    zmq_msg_t requisition_msg;
-    zmq_msg_t data_msg;
-    gpointer context;
-
-    context = ufo_scheduler_get_context (priv->scheduler);
-
-    /* Receive buffer size */
-    zmq_msg_init (&requisition_msg);
-    zmq_msg_recv (&requisition_msg, priv->socket, 0);
-    g_assert (zmq_msg_size (&requisition_msg) >= sizeof (UfoRequisition));
-    requisition = zmq_msg_data (&requisition_msg);
-
-    if (priv->input == NULL) {
-        priv->input = ufo_buffer_new (requisition, context);
-    }
-    else {
-        if (ufo_buffer_cmp_dimensions (priv->input, requisition))
-            ufo_buffer_resize (priv->input, requisition);
-    }
-
-    zmq_msg_close (&requisition_msg);
-
-    /* Receive actual buffer */
-    zmq_msg_init (&data_msg);
-    zmq_msg_recv (&data_msg, priv->socket, 0);
-
-    memcpy (ufo_buffer_get_host_array (priv->input, NULL),
-            zmq_msg_data (&data_msg),
-            ufo_buffer_get_size (priv->input));
-
-    ufo_input_task_release_input_buffer (UFO_INPUT_TASK (priv->input_task), priv->input);
-    zmq_msg_close (&data_msg);
-
-    send_ack (priv->socket);
-}
-
-static void
-handle_get_requisition (ServerPrivate *priv)
-{
-    UfoRequisition requisition;
-    zmq_msg_t reply_msg;
-
-    /* We need to get the requisition from the last node */
-    g_message ("Requisition requested");
-    ufo_output_task_get_output_requisition (UFO_OUTPUT_TASK (priv->output_task),
-                                            &requisition);
-
-    zmq_msg_init_size (&reply_msg, sizeof (UfoRequisition));
-    memcpy (zmq_msg_data (&reply_msg), &requisition, sizeof (UfoRequisition));
-    zmq_msg_send (&reply_msg, priv->socket, 0);
-    zmq_msg_close (&reply_msg);
-}
-
-static
-void handle_get_result (ServerPrivate *priv)
-{
-    UfoBuffer *buffer;
-    zmq_msg_t reply_msg;
-    gsize size;
-
-    buffer = ufo_output_task_get_output_buffer (UFO_OUTPUT_TASK (priv->output_task));
-    size = ufo_buffer_get_size (buffer);
-
-    zmq_msg_init_size (&reply_msg, size);
-    memcpy (zmq_msg_data (&reply_msg), ufo_buffer_get_host_array (buffer, NULL), size);
-    zmq_msg_send (&reply_msg, priv->socket, 0);
-    zmq_msg_close (&reply_msg);
-    ufo_output_task_release_output_buffer (UFO_OUTPUT_TASK (priv->output_task), buffer);
-}
-
-static void
-unref_and_free (GObject **object)
-{
-    if (*object) {
-        g_object_unref (*object);
-        *object = NULL;
-    }
-}
-
-static
-void handle_cleanup (ServerPrivate *priv)
-{
-    /*
-     * We send the ACK early on, because we don't want to let the host wait for
-     * actually cleaning up (and waiting some time to unref the input task).
-     */
-    send_ack (priv->socket);
-
-    if (priv->input_task) {
-        ufo_input_task_stop (UFO_INPUT_TASK (priv->input_task));
-
-        ufo_input_task_release_input_buffer (UFO_INPUT_TASK (priv->input_task),
-                                             priv->input);
-
-        g_usleep (1.5 * G_USEC_PER_SEC);
-        unref_and_free ((GObject **) &priv->input_task);
-        unref_and_free ((GObject **) &priv->input);
-    }
-
-    unref_and_free ((GObject **) &priv->output_task);
-    unref_and_free ((GObject **) &priv->task_graph);
-}
-
-static gpointer
-run_scheduler (ServerPrivate *priv)
-{
-    g_message ("Start scheduler");
-    ufo_scheduler_run (priv->scheduler, priv->task_graph, NULL);
-
-    g_message ("Done");
-    g_object_unref (priv->scheduler);
-
-    priv->scheduler = ufo_scheduler_new (priv->config, NULL);
-    return NULL;
-}
-
 static Options *
 opts_parse (gint *argc, gchar ***argv)
 {
@@ -354,7 +79,7 @@ opts_parse (gint *argc, gchar ***argv)
 }
 
 static UfoConfig *
-opts_new_config (Options *opts)
+opts_new_config (const Options *opts)
 {
     UfoConfig *config;
     GList *paths = NULL;
@@ -363,7 +88,7 @@ opts_new_config (Options *opts)
 
     if (opts->paths != NULL) {
         for (guint i = 0; opts->paths[i] != NULL; i++)
-            paths = g_list_append (paths, opts->paths[i]);
+            paths = g_list_append (paths, g_strdup (opts->paths[i]));
 
         ufo_config_add_paths (config, paths);
         g_list_free (paths);
@@ -380,12 +105,25 @@ opts_free (Options *opts)
     g_free (opts);
 }
 
+static void
+terminate (int signum)
+{
+    if (signum == SIGTERM)
+        g_print ("Received SIGTERM, exiting...\n");
+    if (signum == SIGINT)
+        g_print ("Received SIGINT, exiting...\n");
+
+    if (daemon != NULL) {
+        ufo_daemon_stop(daemon);
+        g_object_unref (daemon);
+    }
+    exit (EXIT_SUCCESS);
+}
+
 int
 main (int argc, char * argv[])
 {
-    gpointer context;
     Options *opts;
-    ServerPrivate priv;
 
     g_type_init ();
     g_thread_init (NULL);
@@ -393,75 +131,21 @@ main (int argc, char * argv[])
     if ((opts = opts_parse (&argc, &argv)) == NULL)
         return 1;
 
-    memset (&priv, 0, sizeof (ServerPrivate));
-
-    priv.config = opts_new_config (opts);
-    priv.manager = ufo_plugin_manager_new (priv.config);
-    priv.scheduler = ufo_scheduler_new (priv.config, NULL);
+    /* Now, install SIGTERM/SIGINT handler */
+    (void) signal (SIGTERM, terminate);
+    (void) signal (SIGINT, terminate);
 
-    /* start zmq service */
-    context = zmq_ctx_new ();
-    priv.socket = zmq_socket (context, ZMQ_REP);
-    zmq_bind (priv.socket, opts->addr);
+    UfoConfig *config = opts_new_config (opts);
 
-    g_print ("ufod %s - waiting for requests ...\n", UFO_VERSION);
+    daemon = ufo_daemon_new (config, opts->addr);
+    ufo_daemon_start(daemon);
+    g_print ("ufod %s - waiting for requests on %s ...\n", UFO_VERSION,
+                                                           opts->addr);
 
-    while (1) {
-        zmq_msg_t request;
-
-        zmq_msg_init (&request);
-        zmq_msg_recv (&request, priv.socket, 0);
-
-        if (zmq_msg_size (&request) < sizeof (UfoMessage)) {
-            g_warning ("Message is smaller than expected\n");
-            send_ack (priv.socket);
-        }
-        else {
-            UfoMessage *msg;
-
-            msg = (UfoMessage *) zmq_msg_data (&request);
-
-            switch (msg->type) {
-                case UFO_MESSAGE_GET_NUM_DEVICES:
-                    handle_get_num_devices (&priv);
-                    break;
-                case UFO_MESSAGE_TASK_JSON:
-                    handle_json (&priv);
-                    break;
-                case UFO_MESSAGE_SETUP:
-                    handle_setup (&priv);
-                    break;
-                case UFO_MESSAGE_GET_STRUCTURE:
-                    handle_get_structure (&priv);
-                    break;
-                case UFO_MESSAGE_SEND_INPUTS:
-                    handle_send_inputs (&priv);
-                    break;
-                case UFO_MESSAGE_GET_REQUISITION:
-                    handle_get_requisition (&priv);
-                    break;
-                case UFO_MESSAGE_GET_RESULT:
-                    handle_get_result (&priv);
-                    break;
-                case UFO_MESSAGE_CLEANUP:
-                    handle_cleanup (&priv);
-                    break;
-                default:
-                    g_print ("unhandled case\n");
-            }
-        }
-
-        zmq_msg_close (&request);
+    while (TRUE) {
+        g_usleep (G_MAXULONG);
     }
 
-    g_object_unref (priv.task_graph);
-    g_object_unref (priv.config);
-    g_object_unref (priv.manager);
-    g_object_unref (priv.scheduler);
-
-    zmq_close (priv.socket);
-    zmq_ctx_destroy (context);
-
     opts_free (opts);
 
     return 0;
diff --git a/ufo/CMakeLists.txt b/ufo/CMakeLists.txt
index 6dc6dee..8573fc6 100644
--- a/ufo/CMakeLists.txt
+++ b/ufo/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 2.6)
 
-# --- Set sources -------------------------------------------------------------
+#{{{ Sources
 set(ufocore_SRCS
     ufo-arch-graph.c
     ufo-buffer.c
@@ -8,12 +8,14 @@ set(ufocore_SRCS
     ufo-config.c
     ufo-cpu-node.c
     ufo-cpu-task-iface.c
+    ufo-daemon.c
     ufo-dummy-task.c
     ufo-gpu-node.c
     ufo-gpu-task-iface.c
     ufo-graph.c
     ufo-group.c
     ufo-input-task.c
+    ufo-messenger-iface.c
     ufo-node.c
     ufo-output-task.c
     ufo-plugin-manager.c
@@ -25,8 +27,12 @@ set(ufocore_SRCS
     ufo-task-iface.c
     ufo-task-graph.c
     ufo-task-node.c
+    ufo-basic-ops.c
+    ufo-zmq-messenger.c
     )
+#}}}
 
+#{{{ Public headers
 set(ufocore_HDRS
     ufo-arch-graph.h
     ufo-buffer.h
@@ -34,12 +40,14 @@ set(ufocore_HDRS
     ufo-config.h
     ufo-cpu-node.h
     ufo-cpu-task-iface.h
+    ufo-daemon.h
     ufo-dummy-task.h
     ufo-gpu-node.h
     ufo-gpu-task-iface.h
     ufo-graph.h
     ufo-group.h
     ufo-input-task.h
+    ufo-messenger-iface.h
     ufo-node.h
     ufo-output-task.h
     ufo-plugin-manager.h
@@ -51,44 +59,32 @@ set(ufocore_HDRS
     ufo-task-iface.h
     ufo-task-graph.h
     ufo-task-node.h
+    ufo-basic-ops.h
+    ufo-zmq-messenger.h
     )
+#}}}
 
+if(WITH_MPI)
+    set(ufocore_HDRS ${ufocore_HDRS}
+        ufo-mpi-messenger.h
+    )
+    set(ufocore_SRCS ${ufocore_SRCS}
+        ufo-mpi-messenger.c
+    )
+endif ()
 
-# --- Find packages and libraries ---------------------------------------------
-find_program(INTROSPECTION_SCANNER "g-ir-scanner")
-find_program(INTROSPECTION_COMPILER "g-ir-compiler")
-find_program(GLIB2_MKENUMS glib-mkenums REQUIRED)
-
-# --- Add enum generation targets ---------------------------------------------
-add_custom_command(
-    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
-    COMMAND ${GLIB2_MKENUMS}
-    ARGS
-        --template ufo-enums.h.template
-        ${ufocore_HDRS} > ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
-    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-    DEPENDS ${ufocore_HDRS}
-            ${CMAKE_CURRENT_SOURCE_DIR}/ufo-enums.h.template
-)
-
-add_custom_command(
-    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.c
-    COMMAND ${GLIB2_MKENUMS}
-    ARGS
-        --template ufo-enums.c.template
-        ${ufocore_HDRS} > ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.c
-    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-    DEPENDS ${ufocore_HDRS} ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
-            ${CMAKE_CURRENT_SOURCE_DIR}/ufo-enums.c.template
-)
+#{{{ Optional dependencies
+pkg_check_modules(PYTHON python)
 
-# --- Target ------------------------------------------------------------------
-get_property(LIB64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS)
+if(PYTHON_FOUND)
+    set(HAVE_PYTHON "1")
+endif()
+#}}}
 
-set(LIBDIR      "lib${LIB_SUFFIX}")
-set(INCLUDEDIR  "include/ufo")
+#{{{ libufo target
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+               ${CMAKE_CURRENT_BINARY_DIR}/config.h)
 
-include_directories("${CMAKE_CURRENT_BINARY_DIR}")
 add_definitions(-DUFO_COMPILATION)
 
 if(CMAKE_BUILD_TYPE MATCHES "Release")
@@ -100,43 +96,64 @@ add_library(ufo SHARED
             ${ufocore_NODOC_SRCS}
             ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.c)
 
+if(PYTHON_FOUND)
+    include_directories(${PYTHON_INCLUDE_DIRS})
+    target_link_libraries(ufo ${PYTHON_LIBRARIES})
+endif()
+
 set_target_properties(ufo PROPERTIES
     VERSION ${PACKAGE_VERSION}
     SOVERSION ${UFO_SO_VERSION})
 
 target_link_libraries(ufo ${UFOCORE_DEPS})
 
+#{{{ install target
 install(TARGETS ufo
-        ARCHIVE DESTINATION ${LIBDIR}
-        LIBRARY DESTINATION ${LIBDIR})
+        ARCHIVE DESTINATION ${UFO_LIBDIR}
+        LIBRARY DESTINATION ${UFO_LIBDIR})
 
 install(FILES ${ufocore_HDRS}
               ${CMAKE_CURRENT_SOURCE_DIR}/ufo.h
               ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
-        DESTINATION ${INCLUDEDIR})
+        DESTINATION ${UFO_INCLUDEDIR}/ufo)
 
+install(FILES ufo-basic-ops.cl
+        DESTINATION ${UFO_LIBDIR}/ufo)
+#}}}
+#}}}
 
-# --- pkg-config --------------------------------------------------------------
-set(UFO_PKG_PREFIX      "${CMAKE_INSTALL_PREFIX}")
-set(UFO_PKG_EXEC_PREFIX "${UFO_PKG_PREFIX}/bin")
-set(UFO_PKG_INCLUDEDIR  "${UFO_PKG_PREFIX}/include")
-set(UFO_PKG_GIRDIR      "${UFO_PKG_PREFIX}/share/gir-1.0")
-set(UFO_PKG_LIBDIR      "${UFO_PKG_PREFIX}/${LIBDIR}")
-set(UFO_PKG_TYPELIBDIR  "${UFO_PKG_PREFIX}/${LIBDIR}/girepository-1.0")
+#{{{ glib-mkenums targets
+find_program(GLIB2_MKENUMS glib-mkenums REQUIRED)
 
-# FIXME: inside the ufo.pc.in we should set the lib names that we found out, not
-# hard coded values
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/ufo.pc.in"
-               "${CMAKE_CURRENT_BINARY_DIR}/ufo.pc" @ONLY IMMEDIATE)
+add_custom_command(
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
+    COMMAND ${GLIB2_MKENUMS}
+    ARGS
+        --template ufo-enums.h.template
+        ${ufocore_HDRS} > ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    DEPENDS ${ufocore_HDRS}
+            ${CMAKE_CURRENT_SOURCE_DIR}/ufo-enums.h.template
+)
 
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ufo.pc
-        DESTINATION ${LIBDIR}/pkgconfig)
+add_custom_command(
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.c
+    COMMAND ${GLIB2_MKENUMS}
+    ARGS
+        --template ufo-enums.c.template
+        ${ufocore_HDRS} > ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.c
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    DEPENDS ${ufocore_HDRS} ${CMAKE_CURRENT_BINARY_DIR}/ufo-enums.h
+            ${CMAKE_CURRENT_SOURCE_DIR}/ufo-enums.c.template
+)
+#}}}
 
+#{{{ g-ir-scanner and g-ir-compiler targets
+find_program(INTROSPECTION_SCANNER "g-ir-scanner")
+find_program(INTROSPECTION_COMPILER "g-ir-compiler")
 
-# --- Introspection files -----------------------------------------------------
 if (INTROSPECTION_SCANNER AND INTROSPECTION_COMPILER)
     option(WITH_GIR "Build introspection files" ON)
-
     if (WITH_GIR)
         set(GIR_PREFIX "Ufo-${UFO_GIR_VERSION}")
         set(GIR_XML "${GIR_PREFIX}.gir")
@@ -161,7 +178,8 @@ if (INTROSPECTION_SCANNER AND INTROSPECTION_COMPILER)
                     -DUFO_COMPILATION
                     --output ${GIR_XML}
                     --warn-all
-                    ${_gir_input} > /dev/null
+                    --quiet
+                    ${_gir_input} 2> /dev/null
             DEPENDS ${ufocore_SRCS}
             WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
 
@@ -176,19 +194,15 @@ if (INTROSPECTION_SCANNER AND INTROSPECTION_COMPILER)
         add_dependencies(gir ufo)
 
         install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${GIR_XML}
-            DESTINATION share/gir-1.0)
+                DESTINATION ${UFO_GIRDIR})
 
         install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${GIR_TYPELIB}
-            DESTINATION ${LIBDIR}/girepository-1.0)
+                DESTINATION ${UFO_TYPELIBDIR})
     endif()
 endif()
+#}}}
 
-
-# --- Generate config.h -------------------------------------------------------
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
-
-
-# --- Build API reference -----------------------------------------------------
+#{{{ gtk-doc targets
 pkg_check_modules(GTK_DOC gtk-doc)
 if(GTK_DOC_FOUND)
     option(WITH_GTK_DOC "Build API reference" ON)
@@ -218,6 +232,11 @@ if(GTK_DOC_FOUND)
             endif()
         endforeach()
 
+        get_directory_property(_current_link_dirs LINK_DIRECTORIES)
+        foreach(_linkdir ${_current_link_dirs})
+            set(GTK_DOC_LDFLAGS "-L${_linkdir} ${GTK_DOC_LDFLAGS}")
+        endforeach()
+
         configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../docs/Ufo-docs.xml.in" "${docs_out}/Ufo-docs.xml")
         configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../docs/scangobj.sh.in" "${docs_out}/scangobj.sh")
 
@@ -240,6 +259,7 @@ if(GTK_DOC_FOUND)
             COMMAND ${GTK_DOC_SCAN}
                     --module=Ufo
                     --source-dir=${CMAKE_CURRENT_SOURCE_DIR}
+                    --ignore-headers=ufo-mpi-messenger.h
                     DEPENDS ${ufocore_SRCS}
             WORKING_DIRECTORY ${docs_out})
 
@@ -269,3 +289,14 @@ if(GTK_DOC_FOUND)
         install(FILES ${reference_files} DESTINATION share/gtk-doc/html/Ufo)
     endif()
 endif(GTK_DOC_FOUND)
+#}}}
+
+#{{{ pkg-config
+# FIXME: inside the ufo.pc.in we should set the lib names that we found out, not
+# hard coded values
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/ufo.pc.in"
+               "${CMAKE_CURRENT_BINARY_DIR}/ufo.pc" @ONLY IMMEDIATE)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ufo.pc
+        DESTINATION ${UFO_PKGCONFIGDIR})
+#}}}
diff --git a/ufo/config.h.in b/ufo/config.h.in
index a3cada3..2f34fda 100644
--- a/ufo/config.h.in
+++ b/ufo/config.h.in
@@ -1,3 +1,5 @@
 #cmakedefine WITH_PROFILING 1
-#define UFO_PLUGIN_DIR "${CMAKE_INSTALL_PREFIX}/${LIBDIR}/ufo"
+#cmakedefine HAVE_VIENNACL 1
+#cmakedefine HAVE_PYTHON 1
+#define UFO_PLUGIN_DIR "${UFO_LIBDIR}/ufo"
 #define UFO_VERSION "${PACKAGE_VERSION}"
diff --git a/ufo/ufo-arch-graph.c b/ufo/ufo-arch-graph.c
index 9e44f1f..b1608ba 100644
--- a/ufo/ufo-arch-graph.c
+++ b/ufo/ufo-arch-graph.c
@@ -39,8 +39,6 @@ G_DEFINE_TYPE (UfoArchGraph, ufo_arch_graph, UFO_TYPE_GRAPH)
 
 struct _UfoArchGraphPrivate {
     UfoResources *resources;
-    gpointer zmq_context;
-    gpointer ocl_context;
     UfoNode **cpu_nodes;
     UfoNode **gpu_nodes;
     UfoNode **remote_nodes;
@@ -71,7 +69,6 @@ ufo_arch_graph_new (UfoResources *resources,
 
     g_object_ref (resources);
     priv->resources = resources;
-    priv->ocl_context = ufo_resources_get_context (resources);
 
     /* Create CPU nodes */
     priv->n_cpus = (guint) get_nprocs ();
@@ -96,12 +93,10 @@ ufo_arch_graph_new (UfoResources *resources,
     priv->n_remotes = g_list_length (remote_addresses);
 
     if (priv->n_remotes > 0) {
-        priv->zmq_context = zmq_ctx_new ();
         priv->remote_nodes = g_new0 (UfoNode *, priv->n_remotes);
 
         for (guint i = 0; i < priv->n_remotes; i++) {
-            priv->remote_nodes[i] = ufo_remote_node_new (priv->zmq_context,
-                                                         (gchar *) g_list_nth_data (remote_addresses, i));
+            priv->remote_nodes[i] = ufo_remote_node_new ((gchar *) g_list_nth_data (remote_addresses, i));
         }
     }
 
@@ -248,12 +243,6 @@ ufo_arch_graph_finalize (GObject *object)
 
     priv = UFO_ARCH_GRAPH_GET_PRIVATE (object);
 
-    if (priv->zmq_context != NULL) {
-        g_debug ("Destroy zmq_context=%p", priv->zmq_context);
-        zmq_ctx_destroy (priv->zmq_context);
-        priv->zmq_context = NULL;
-    }
-
     g_free (priv->cpu_nodes);
     g_free (priv->gpu_nodes);
     g_free (priv->remote_nodes);
@@ -285,8 +274,4 @@ ufo_arch_graph_init (UfoArchGraph *self)
     priv->cpu_nodes = NULL;
     priv->gpu_nodes = NULL;
     priv->remote_nodes = NULL;
-
-    ufo_graph_register_node_type (UFO_GRAPH (self), UFO_TYPE_CPU_NODE);
-    ufo_graph_register_node_type (UFO_GRAPH (self), UFO_TYPE_GPU_NODE);
-    ufo_graph_register_node_type (UFO_GRAPH (self), UFO_TYPE_REMOTE_NODE);
 }
diff --git a/ufo/ufo-basic-ops.c b/ufo/ufo-basic-ops.c
new file mode 100644
index 0000000..44c9686
--- /dev/null
+++ b/ufo/ufo-basic-ops.c
@@ -0,0 +1,681 @@
+#ifdef __APPLE__
+#include <OpenCL/cl.h>
+#else
+#include <CL/cl.h>
+#endif
+
+#include <math.h>
+#include <ufo/ufo-basic-ops.h>
+#define OPS_FILENAME "ufo-basic-ops.cl"
+
+static cl_event
+operation (const gchar *kernel_name,
+           UfoBuffer *arg1,
+           UfoBuffer *arg2,
+           UfoBuffer *out,
+           UfoResources *resources,
+           gpointer command_queue);
+
+static cl_event
+operation2 (const gchar *kernel_name,
+            UfoBuffer *arg1,
+            UfoBuffer *arg2,
+            gfloat modifier,
+            UfoBuffer *out,
+            UfoResources *resources,
+            gpointer command_queue);
+
+/**
+ * ufo_op_set:
+ * @arg: A #UfoBuffer
+ * @value: Value to fill @arg with
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Fill a buffer with a value using OpenCL.
+ *
+ * Returns: (transfer full): Event of the set operation
+ */
+gpointer
+ufo_op_set (UfoBuffer *arg,
+            gfloat value,
+            UfoResources *resources,
+            gpointer command_queue)
+{
+    UfoRequisition requisition;
+    cl_kernel kernel;
+    cl_mem d_arg;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg, &requisition);
+    d_arg = ufo_buffer_get_device_image (arg, command_queue);
+    kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "operation_set", &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+        return NULL;
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 0, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 1, sizeof(gfloat), (void *) &value));
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       requisition.n_dims, NULL, requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+/**
+ * ufo_op_inv:
+ * @arg: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Invert @arg.
+ *
+ * Returns: (transfer full): Event of the invert operation
+ */
+gpointer
+ufo_op_inv (UfoBuffer *arg,
+            UfoResources *resources,
+            gpointer command_queue)
+{
+    UfoRequisition requisition;
+    cl_event event;
+    cl_kernel kernel;
+    cl_mem d_arg;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg, &requisition);
+
+    d_arg = ufo_buffer_get_device_image (arg, command_queue);
+    kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "operation_inv", &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+        return NULL;
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 0, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 1, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel(command_queue, kernel,
+                                                      requisition.n_dims, NULL, requisition.dims,
+                                                      NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+/**
+ * ufo_op_mul:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * out = arg1 * arg2
+ *
+ * Returns: (transfer full): Event of the mul operation
+ */
+gpointer
+ufo_op_mul (UfoBuffer *arg1,
+            UfoBuffer *arg2,
+            UfoBuffer *out,
+            UfoResources *resources,
+            gpointer command_queue)
+{
+    return operation ("operation_mul", arg1, arg2, out, resources, command_queue);
+}
+
+/**
+ * ufo_op_add:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * out = arg1 + arg2
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_add (UfoBuffer *arg1,
+            UfoBuffer *arg2,
+            UfoBuffer *out,
+            UfoResources *resources,
+            gpointer command_queue)
+{
+    return operation ("operation_add", arg1, arg2, out, resources, command_queue);
+}
+
+/**
+ * ufo_op_add2:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @modifier: Scalar value
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * @out = @arg1 + @modifier * @arg2
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_add2 (UfoBuffer *arg1,
+             UfoBuffer *arg2,
+             gfloat modifier,
+             UfoBuffer *out,
+             UfoResources *resources,
+             gpointer command_queue)
+{
+    return operation2 ("operation_add2", arg1, arg2, modifier, out, resources, command_queue);
+}
+
+/**
+ * ufo_op_deduction:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * @out = @arg1 - @arg2
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_deduction (UfoBuffer *arg1,
+                  UfoBuffer *arg2,
+                  UfoBuffer *out,
+                  UfoResources *resources,
+                  gpointer command_queue)
+{
+    return operation ("operation_deduction", arg1, arg2, out, resources, command_queue);
+}
+
+/**
+ * ufo_op_deduction2:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @modifier: Scalar value
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * @out = @arg1 - @modifier * @arg2
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_deduction2 (UfoBuffer *arg1,
+                   UfoBuffer *arg2,
+                   gfloat modifier,
+                   UfoBuffer *out,
+                   UfoResources *resources,
+                   gpointer command_queue)
+{
+    return operation2 ("operation_deduction2", arg1, arg2, modifier, out, resources, command_queue);
+}
+
+/**
+ * ufo_op_mul_rows:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @offset: Offset
+ * @n: n ?
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * @out = @arg1 - @modifier * @arg2
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_mul_rows (UfoBuffer *arg1,
+                 UfoBuffer *arg2,
+                 UfoBuffer *out,
+                 guint offset,
+                 guint n,
+                 UfoResources *resources,
+                 gpointer command_queue)
+{
+    cl_event event;
+    UfoRequisition arg1_requisition, arg2_requisition, out_requisition;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg1, &arg1_requisition);
+    ufo_buffer_get_requisition (arg2, &arg2_requisition);
+    ufo_buffer_get_requisition (out, &out_requisition);
+
+    if (arg1_requisition.dims[0] != arg2_requisition.dims[0] ||
+        arg1_requisition.dims[0] != out_requisition.dims[0]) {
+        g_error ("Number of columns is different.");
+        return NULL;
+    }
+
+    if (arg1_requisition.dims[1] < offset + n ||
+        arg2_requisition.dims[1] < offset + n ||
+        out_requisition.dims[1] < offset + n) {
+        g_error ("Rows are not enough.");
+        return NULL;
+    }
+
+    cl_mem d_arg1 = ufo_buffer_get_device_image (arg1, command_queue);
+    cl_mem d_arg2 = ufo_buffer_get_device_image (arg2, command_queue);
+    cl_mem d_out  = ufo_buffer_get_device_image (out, command_queue);
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "op_mulRows", &error);
+
+    if (error != NULL) {
+        g_error ("Error: %s\n", error->message);
+        return NULL;
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 0, sizeof(void *), (void *) &d_arg1));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 1, sizeof(void *), (void *) &d_arg2));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 2, sizeof(void *), (void *) &d_out));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 3, sizeof(unsigned int), (void *) &offset));
+
+    UfoRequisition operation_requisition = out_requisition;
+    operation_requisition.dims[1] = n;
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       operation_requisition.n_dims, NULL, operation_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+static cl_event
+operation (const gchar *kernel_name,
+           UfoBuffer *arg1,
+           UfoBuffer *arg2,
+           UfoBuffer *out,
+           UfoResources *resources,
+           gpointer command_queue)
+{
+    UfoRequisition arg1_requisition, arg2_requisition, out_requisition;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg1, &arg1_requisition);
+    ufo_buffer_get_requisition (arg2, &arg2_requisition);
+    ufo_buffer_get_requisition (out, &out_requisition);
+
+    if ((arg1_requisition.dims[0] != arg2_requisition.dims[0] &&
+         arg1_requisition.dims[0] != out_requisition.dims[0]) ||
+        (arg1_requisition.dims[1] != arg2_requisition.dims[1] &&
+         arg1_requisition.dims[1] != out_requisition.dims[1])) {
+        g_error ("Incorrect volume size.");
+        return NULL;
+    }
+
+    cl_mem d_arg1 = ufo_buffer_get_device_image (arg1, command_queue);
+    cl_mem d_arg2 = ufo_buffer_get_device_image (arg2, command_queue);
+    cl_mem d_out = ufo_buffer_get_device_image (out, command_queue);
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, kernel_name, &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+        return NULL;
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 0, sizeof(void *), (void *) &d_arg1));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 1, sizeof(void *), (void *) &d_arg2));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 2, sizeof(void *), (void *) &d_out));
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       arg1_requisition.n_dims, NULL, arg1_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+static cl_event
+operation2 (const gchar *kernel_name,
+            UfoBuffer *arg1,
+            UfoBuffer *arg2,
+            gfloat modifier,
+            UfoBuffer *out,
+            UfoResources *resources,
+            gpointer command_queue)
+{
+    UfoRequisition arg1_requisition, arg2_requisition, out_requisition;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg1, &arg1_requisition);
+    ufo_buffer_get_requisition (arg2, &arg2_requisition);
+    ufo_buffer_get_requisition (out, &out_requisition);
+
+    if ((arg1_requisition.dims[0] != arg2_requisition.dims[0] &&
+         arg1_requisition.dims[0] != out_requisition.dims[0]) ||
+        (arg1_requisition.dims[1] != arg2_requisition.dims[1] &&
+         arg1_requisition.dims[1] != out_requisition.dims[1])) {
+        g_error ("Incorrect volume size.");
+        return NULL;
+    }
+
+    cl_mem d_arg1 = ufo_buffer_get_device_image (arg1, command_queue);
+    cl_mem d_arg2 = ufo_buffer_get_device_image (arg2, command_queue);
+    cl_mem d_out = ufo_buffer_get_device_image (out, command_queue);
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, kernel_name, &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+        return NULL;
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 0, sizeof(void *), (void *) &d_arg1));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 1, sizeof(void *), (void *) &d_arg2));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 2, sizeof(gfloat), (void *) &modifier));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 3, sizeof(void *), (void *) &d_out));
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       arg1_requisition.n_dims, NULL, arg1_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+/**
+ * ufo_op_gradient_magnitudes:
+ * @arg: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Compute magnitude of gradients
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_gradient_magnitudes (UfoBuffer *arg,
+                            UfoBuffer *out,
+                            UfoResources *resources,
+                            gpointer command_queue)
+{
+    UfoRequisition arg_requisition;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg, &arg_requisition);
+    ufo_buffer_resize (out, &arg_requisition);
+
+    cl_mem d_arg = ufo_buffer_get_device_image (arg, command_queue);
+    cl_mem d_out = ufo_buffer_get_device_image (out, command_queue);
+
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "operation_gradient_magnitude", &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 0, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 1, sizeof(void *), (void *) &d_out));
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       arg_requisition.n_dims, NULL, arg_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+/**
+ * ufo_op_gradient_directions:
+ * @arg: A #UfoBuffer
+ * @magnitudes: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Compute magnitude of gradients
+ *
+ * Returns: (transfer full): Event of the add operation
+ */
+gpointer
+ufo_op_gradient_directions (UfoBuffer *arg,
+                            UfoBuffer *magnitudes,
+                            UfoBuffer *out,
+                            UfoResources *resources,
+                            gpointer command_queue)
+{
+    UfoRequisition arg_requisition;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg, &arg_requisition);
+    ufo_buffer_resize (out, &arg_requisition);
+
+    cl_mem d_arg = ufo_buffer_get_device_image (arg, command_queue);
+    cl_mem d_magnitudes = ufo_buffer_get_device_image (magnitudes, command_queue);
+    cl_mem d_out = ufo_buffer_get_device_image (out, command_queue);
+
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "operation_gradient_direction", &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 0, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 1, sizeof(void *), (void *) &d_magnitudes));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 2, sizeof(void *), (void *) &d_out));
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       arg_requisition.n_dims, NULL, arg_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+/**
+ * ufo_op_l1_norm:
+ * @arg: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Returns: L1 norm.
+ */
+gfloat
+ufo_op_l1_norm (UfoBuffer *arg,
+                UfoResources *resources,
+                gpointer command_queue)
+{
+    UfoRequisition arg_requisition;
+    gfloat *values;
+    gfloat norm = 0;
+
+    ufo_buffer_get_requisition (arg, &arg_requisition);
+    values = ufo_buffer_get_host_array (arg, command_queue);
+
+    for (guint i = 0; i < arg_requisition.dims[0]; ++i) {
+        for (guint j = 0; j < arg_requisition.dims[1]; ++j) {
+            norm += (gfloat) fabs (values[i * arg_requisition.dims[0] + j]);
+        }
+    }
+
+    return norm;
+}
+
+/**
+ * ufo_op_euclidean_distance:
+ * @arg1: A #UfoBuffer
+ * @arg2: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Returns: Euclidean distance between @arg1 and @arg2.
+ */
+gfloat
+ufo_op_euclidean_distance (UfoBuffer *arg1,
+                           UfoBuffer *arg2,
+                           UfoResources *resources,
+                           gpointer command_queue)
+{
+    UfoRequisition arg1_requisition, arg2_requisition;
+    guint length;
+    gfloat diff;
+    gfloat norm = 0;
+    guint length1 = 0;
+    guint length2 = 0;
+    gfloat *values1;
+    gfloat *values2;
+
+    ufo_buffer_get_requisition (arg1, &arg1_requisition);
+    ufo_buffer_get_requisition (arg2, &arg2_requisition);
+
+    for (guint i = 0; i < arg1_requisition.n_dims; ++i)
+        length1 += (guint)arg1_requisition.dims[i];
+
+    for (guint i = 0; i < arg2_requisition.n_dims; ++i)
+        length2 += (guint)arg2_requisition.dims[i];
+
+    if (length2 != length1)
+        g_warning ("Sizes of buffers are not the same. Zero-padding applied.");
+
+    length = length2 < length1 ? length2 : length1;
+    values1 = ufo_buffer_get_host_array (arg1, command_queue);
+    values2 = ufo_buffer_get_host_array (arg2, command_queue);
+
+    for (guint i = 0; i < length; ++i) {
+        diff = values1[i] - values2[i];
+        norm += powf (diff, 2);
+    }
+
+    for (guint i = length; i < length2; ++i)
+        norm += powf (values2[i], 2);
+
+    for (guint i = length; i < length1; ++i)
+        norm += powf (values1[i], 2);
+
+    norm = sqrtf(norm);
+    return norm;
+}
+
+/**
+ * ufo_op_l2_norm:
+ * @arg: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Returns: L2 norm.
+ */
+gfloat
+ufo_op_l2_norm (UfoBuffer *arg,
+                UfoResources *resources,
+                gpointer command_queue)
+{
+    return ufo_op_euclidean_distance (arg, arg, resources, command_queue);
+}
+
+/**
+ * ufo_op_POSC:
+ * @arg: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Returns: (transfer full): Event of the POSC operation
+ */
+gpointer
+ufo_op_POSC (UfoBuffer *arg,
+             UfoBuffer *out,
+             UfoResources *resources,
+             gpointer command_queue)
+{
+    UfoRequisition arg_requisition;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg, &arg_requisition);
+    ufo_buffer_resize (out, &arg_requisition);
+
+    cl_mem d_arg = ufo_buffer_get_device_image (arg, command_queue);
+    cl_mem d_out = ufo_buffer_get_device_image (out, command_queue);
+
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "POSC", &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 0, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg (kernel, 1, sizeof(void *), (void *) &d_out));
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       arg_requisition.n_dims, NULL, arg_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
+
+/**
+ * ufo_op_gradient_descent:
+ * @arg: A #UfoBuffer
+ * @out: A #UfoBuffer
+ * @resources: #UfoResources object
+ * @command_queue: A valid cl_command_queue
+ *
+ * Returns: (transfer full): Event of the POSC operation
+ */
+gpointer
+ufo_op_gradient_descent (UfoBuffer *arg,
+                         UfoBuffer *out,
+                         UfoResources *resources,
+                         gpointer command_queue)
+{
+    UfoRequisition arg_requisition;
+    cl_event event;
+    GError *error = NULL;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    ufo_buffer_get_requisition (arg, &arg_requisition);
+    ufo_buffer_resize (out, &arg_requisition);
+
+    cl_mem d_arg = ufo_buffer_get_device_image (arg, command_queue);
+    cl_mem d_out = ufo_buffer_get_device_image (out, command_queue);
+
+    cl_kernel kernel = ufo_resources_get_cached_kernel (resources, OPS_FILENAME, "descent_grad", &error);
+
+    if (error) {
+        g_error ("%s\n", error->message);
+    }
+
+    g_static_mutex_lock (&mutex);
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 0, sizeof(void *), (void *) &d_arg));
+    UFO_RESOURCES_CHECK_CLERR (clSetKernelArg(kernel, 1, sizeof(void *), (void *) &d_out));
+
+    UFO_RESOURCES_CHECK_CLERR (clEnqueueNDRangeKernel (command_queue, kernel,
+                                                       arg_requisition.n_dims, NULL, arg_requisition.dims,
+                                                       NULL, 0, NULL, &event));
+    g_static_mutex_unlock (&mutex);
+
+    return event;
+}
diff --git a/ufo/ufo-basic-ops.cl b/ufo/ufo-basic-ops.cl
new file mode 100644
index 0000000..a75e63a
--- /dev/null
+++ b/ufo/ufo-basic-ops.cl
@@ -0,0 +1,326 @@
+const sampler_t imageSampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;
+const sampler_t imageSampler2 = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST;
+
+__kernel
+void operation_set (__write_only image2d_t out,
+                    const float value)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+ 
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_inv (__read_only image2d_t in,
+                 		__write_only image2d_t out)
+{
+	const uint X = get_global_id(0);
+	const uint Y = get_global_id(1);
+ 
+ 	float2 coord_r;
+	coord_r.x = (float)X + 0.5f;
+	coord_r.y = (float)Y + 0.5f;
+	
+	int2 coord_w;
+	coord_w.x = X;
+	coord_w.y = Y;
+
+	float value = read_imagef(in, imageSampler, coord_r).s0;
+  value = (value != 0)? 1.0f / value : 0;
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_mul (__read_only image2d_t arg1_r,
+                    __read_only image2d_t arg2_r,
+                    __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  float2 coord_r;
+  coord_r.x = (float)X + 0.5f;
+  coord_r.y = (float)Y + 0.5f;
+  
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float value = read_imagef(arg1_r, imageSampler, coord_r).s0 * 
+                read_imagef(arg2_r, imageSampler, coord_r).s0;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_add (__read_only image2d_t arg1_r,
+                    __read_only image2d_t arg2_r,
+                    __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  float2 coord_r;
+  coord_r.x = (float)X + 0.5f;
+  coord_r.y = (float)Y + 0.5f;
+  
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float value = read_imagef(arg1_r, imageSampler, coord_r).s0 + 
+                read_imagef(arg2_r, imageSampler, coord_r).s0;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_deduction (__read_only image2d_t arg1_r,
+                          __read_only image2d_t arg2_r,
+                          __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  float2 coord_r;
+  coord_r.x = (float)X + 0.5f;
+  coord_r.y = (float)Y + 0.5f;
+  
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float value = read_imagef(arg1_r, imageSampler, coord_r).s0 - 
+                read_imagef(arg2_r, imageSampler, coord_r).s0;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_deduction2 (__read_only image2d_t arg1_r,
+                           __read_only image2d_t arg2_r,
+                           const float  modifier,
+                           __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  float2 coord_r;
+  coord_r.x = (float)X + 0.5f;
+  coord_r.y = (float)Y + 0.5f;
+  
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float value = read_imagef(arg1_r, imageSampler, coord_r).s0 - 
+                modifier * read_imagef(arg2_r, imageSampler, coord_r).s0;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_add2 (__read_only image2d_t arg1_r,
+                     __read_only image2d_t arg2_r,
+                     const float  modifier,
+                     __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  float2 coord_r;
+  coord_r.x = (float)X + 0.5f;
+  coord_r.y = (float)Y + 0.5f;
+  
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float value = read_imagef(arg1_r, imageSampler, coord_r).s0 + 
+                modifier * read_imagef(arg2_r, imageSampler, coord_r).s0;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void op_mulRows (__read_only  image2d_t arg1_r,
+                 __read_only  image2d_t arg2_r,
+                 __write_only image2d_t out,
+                const uint    offset)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  float2 coord_r;
+  coord_r.x = (float)X + 0.5f;
+  coord_r.y = (float)offset + (float)Y + 0.5f;
+  
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = offset + Y;
+
+  float value = read_imagef(arg1_r, imageSampler, coord_r).s0 * 
+                read_imagef(arg2_r, imageSampler, coord_r).s0;
+
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_gradient_magnitude (__read_only image2d_t arg_r,
+                                   __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float2 coord_r[5];
+  coord_r[0].x = (float)X + 0.5f;
+  coord_r[0].y = (float)Y + 0.5f;
+  coord_r[1].x = coord_r[0].x + 1;
+  coord_r[1].y = coord_r[0].y;
+  coord_r[2].x = coord_r[0].x - 1;
+  coord_r[2].y = coord_r[0].y;
+  coord_r[3].x = coord_r[0].x;
+  coord_r[3].y = coord_r[0].y + 1;
+  coord_r[4].x = coord_r[0].x;
+  coord_r[4].y = coord_r[0].y - 1;
+
+  float cell_value = read_imagef(arg_r, imageSampler2, coord_r[0]).s0;
+  float d1 = read_imagef(arg_r, imageSampler2, coord_r[1]).s0 - cell_value;
+  float d2 = read_imagef(arg_r, imageSampler2, coord_r[2]).s0 - cell_value;
+  float d3 = read_imagef(arg_r, imageSampler2, coord_r[3]).s0 - cell_value;
+  float d4 = read_imagef(arg_r, imageSampler2, coord_r[4]).s0 - cell_value;
+
+  float value = sqrt ( (pow (d1, 2) + pow (d2, 2) + pow (d3, 2) + pow (d4, 2)) / 2.0f);
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void operation_gradient_direction (__read_only image2d_t arg_r,
+                                   __read_only image2d_t magnitude,
+                                   __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float2 coord_r[5];
+  coord_r[0].x = (float)X + 0.5f;
+  coord_r[0].y = (float)Y + 0.5f;
+  coord_r[1].x = coord_r[0].x + 1;
+  coord_r[1].y = coord_r[0].y;
+  coord_r[2].x = coord_r[0].x - 1;
+  coord_r[2].y = coord_r[0].y;
+  coord_r[3].x = coord_r[0].x;
+  coord_r[3].y = coord_r[0].y + 1;
+  coord_r[4].x = coord_r[0].x;
+  coord_r[4].y = coord_r[0].y - 1;
+
+  float values[5];
+  values[0] = read_imagef(arg_r, imageSampler2, coord_r[0]).s0;
+  values[1] = read_imagef(arg_r, imageSampler2, coord_r[1]).s0;
+  values[2] = read_imagef(arg_r, imageSampler2, coord_r[2]).s0;
+  values[3] = read_imagef(arg_r, imageSampler2, coord_r[3]).s0;
+  values[4] = read_imagef(arg_r, imageSampler2, coord_r[4]).s0;
+
+  float magnitudes[5];
+  magnitudes[0] = read_imagef(magnitude, imageSampler2, coord_r[0]).s0;
+  magnitudes[1] = read_imagef(magnitude, imageSampler2, coord_r[1]).s0;
+  magnitudes[2] = read_imagef(magnitude, imageSampler2, coord_r[2]).s0;
+  magnitudes[3] = read_imagef(magnitude, imageSampler2, coord_r[3]).s0;
+  magnitudes[4] = read_imagef(magnitude, imageSampler2, coord_r[4]).s0;
+
+  float direction = 0;
+  if (magnitudes[0]) direction += (4 * values[0] - values[1] - values[2] - values[3] - values[4]) / magnitudes[0];
+  if (magnitudes[1]) direction += (values[0] - values[1]) / magnitudes[1];
+  if (magnitudes[2]) direction += (values[0] - values[2]) / magnitudes[2];
+  if (magnitudes[3]) direction += (values[0] - values[3]) / magnitudes[3];
+  if (magnitudes[4]) direction += (values[0] - values[4]) / magnitudes[4];
+
+  write_imagef(out, coord_w, direction);
+}
+
+
+__kernel
+void POSC (__read_only image2d_t arg_r,
+           __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float2 coord_r;
+  coord_r.x = X + 0.5f;
+  coord_r.y = Y + 0.5f;
+
+  float value = read_imagef(arg_r, imageSampler2, coord_r).s0;
+  value = value > 0 ? value : 0;
+  write_imagef(out, coord_w, value);
+}
+
+__kernel
+void descent_grad (__read_only image2d_t arg_r,
+                   __write_only image2d_t out)
+{
+  const uint X = get_global_id(0);
+  const uint Y = get_global_id(1);
+
+  int2 coord_w;
+  coord_w.x = X;
+  coord_w.y = Y;
+
+  float2 coord_r[7];
+  coord_r[0].x = X;
+  coord_r[0].y = Y;
+  coord_r[1].x = coord_r[0].x - 1;
+  coord_r[1].y = coord_r[0].y;
+  coord_r[2].x = coord_r[0].x;
+  coord_r[2].y = coord_r[0].y - 1;
+  coord_r[3].x = coord_r[0].x + 1;
+  coord_r[3].y = coord_r[0].y;
+  coord_r[4].x = coord_r[0].x;
+  coord_r[4].y = coord_r[0].y + 1;
+  coord_r[5].x = coord_r[0].x + 1;
+  coord_r[5].y = coord_r[0].y - 1;
+  coord_r[6].x = coord_r[0].x - 1;
+  coord_r[6].y = coord_r[0].y + 1;
+
+  float eps = 1E-8;
+  float values[7];
+  values[0] = read_imagef(arg_r, imageSampler2, coord_r[0]).s0;
+  values[1] = read_imagef(arg_r, imageSampler2, coord_r[1]).s0;
+  values[2] = read_imagef(arg_r, imageSampler2, coord_r[2]).s0;
+  values[3] = read_imagef(arg_r, imageSampler2, coord_r[3]).s0;
+  values[4] = read_imagef(arg_r, imageSampler2, coord_r[4]).s0;
+  values[5] = read_imagef(arg_r, imageSampler2, coord_r[5]).s0;
+  values[6] = read_imagef(arg_r, imageSampler2, coord_r[6]).s0;
+  
+  float t1, t2;
+  float part[3];
+  t1 = values[0] - values[1];
+  t2 = values[0] - values[2];
+  part[0] = (t1 + t2) / sqrt(eps + pow(t1, 2) + pow (t2, 2));
+  t1 = values[3] - values[0];
+  t2 = values[3] - values[5];
+  part[1] = t1 / sqrt(eps + pow(t1, 2) + pow (t2, 2));
+  t1 = values[4] - values[0];
+  t2 = values[4] - values[6];
+  part[2] = t1 / sqrt(eps + pow(t1, 2) + pow (t2, 2));
+
+  float value = part[0] - part[1] - part[2];
+  write_imagef(out, coord_w, value);
+}
\ No newline at end of file
diff --git a/ufo/ufo-basic-ops.h b/ufo/ufo-basic-ops.h
new file mode 100644
index 0000000..ef08984
--- /dev/null
+++ b/ufo/ufo-basic-ops.h
@@ -0,0 +1,89 @@
+#ifndef __UFO_BASIC_OPS
+#define __UFO_BASIC_OPS
+
+#if !defined (__UFO_H_INSIDE__) && !defined (UFO_COMPILATION)
+#error "Only <ufo/ufo.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <ufo/ufo-buffer.h>
+#include <ufo/ufo-resources.h>
+
+G_BEGIN_DECLS
+
+gpointer ufo_op_set         (UfoBuffer      *arg,
+                             gfloat           value,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_inv         (UfoBuffer      *arg,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_mul         (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_add         (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_add2        (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             gfloat          modifier,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_deduction   (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_deduction2  (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             gfloat          modifier,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_mul_rows    (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             UfoBuffer      *out,
+                             guint           offset,
+                             guint           n,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_gradient_magnitudes
+                            (UfoBuffer      *arg,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_gradient_directions
+                            (UfoBuffer      *arg,
+                             UfoBuffer      *magnitudes,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gfloat ufo_op_l1_norm       (UfoBuffer      *arg,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gfloat ufo_op_l2_norm       (UfoBuffer      *arg,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gfloat ufo_op_euclidean_distance
+                            (UfoBuffer      *arg1,
+                             UfoBuffer      *arg2,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_POSC        (UfoBuffer      *arg,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+gpointer ufo_op_gradient_descent
+                            (UfoBuffer      *arg,
+                             UfoBuffer      *out,
+                             UfoResources   *resources,
+                             gpointer        command_queue);
+
+G_END_DECLS
+
+#endif
diff --git a/ufo/ufo-buffer.c b/ufo/ufo-buffer.c
index cf97967..4910408 100644
--- a/ufo/ufo-buffer.c
+++ b/ufo/ufo-buffer.c
@@ -37,6 +37,13 @@ G_DEFINE_TYPE(UfoBuffer, ufo_buffer, G_TYPE_OBJECT)
 
 #define UFO_BUFFER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_BUFFER, UfoBufferPrivate))
 
+typedef enum {
+    UFO_LOCATION_HOST = 0,
+    UFO_LOCATION_DEVICE,
+    UFO_LOCATION_DEVICE_IMAGE,
+    UFO_LOCATION_INVALID
+} UfoMemLocation;
+
 enum {
     PROP_0,
     PROP_ID,
@@ -44,56 +51,159 @@ enum {
     N_PROPERTIES
 };
 
-typedef struct {
-    guint num_dims;
-    gfloat *data;
-    gsize dim_size[UFO_BUFFER_MAX_NDIMS];
-} nd_array;
-
 struct _UfoBufferPrivate {
-    nd_array            host_array;
+    UfoRequisition      requisition;
+    gfloat             *host_array;
     cl_mem              device_array;
+    cl_mem              device_image;
     cl_context          context;
     cl_command_queue    last_queue;
     gsize               size;   /**< size of buffer in bytes */
     UfoMemLocation      location;
-    GTimer             *timer;
+    UfoMemLocation      last_location;
 };
 
 static void
-alloc_mem (UfoBufferPrivate *priv,
-           UfoRequisition *requisition)
+copy_requisition (UfoRequisition *src,
+                  UfoRequisition *dst)
 {
-    cl_int err;
+    const guint n_dims = src->n_dims;
+
+    dst->n_dims = n_dims;
+
+    for (guint i = 0; i < n_dims; i++)
+        dst->dims[i] = src->dims[i];
+}
+
+static gsize
+compute_required_size (UfoRequisition *requisition)
+{
+    gsize size = sizeof (gfloat);
+
+    for (guint i = 0; i < requisition->n_dims; i++)
+        size *= requisition->dims[i];
 
-    if (priv->host_array.data != NULL)
-        g_free (priv->host_array.data);
+    return size;
+}
+
+static void
+alloc_host_mem (UfoBufferPrivate *priv)
+{
+    if (priv->host_array != NULL)
+        g_free (priv->host_array);
+
+    priv->host_array = g_malloc0 (priv->size);
+}
+
+static void
+alloc_device_array (UfoBufferPrivate *priv)
+{
+    cl_int err;
+    cl_mem mem;
 
     if (priv->device_array != NULL)
-        clReleaseMemObject (priv->device_array);
+        UFO_RESOURCES_CHECK_CLERR (clReleaseMemObject (priv->device_array));
 
-    priv->size = sizeof(gfloat);
-    priv->host_array.num_dims = requisition->n_dims;
+    mem = clCreateBuffer (priv->context,
+                          CL_MEM_READ_WRITE,
+                          priv->size,
+                          NULL, &err);
 
-    for (guint i = 0; i < requisition->n_dims; i++) {
-        priv->host_array.dim_size[i] = requisition->dims[i];
-        priv->size *= requisition->dims[i];
+    UFO_RESOURCES_CHECK_CLERR (err);
+    priv->device_array = mem;
+}
+
+#ifdef CL_VERSION_1_2
+static void
+alloc_device_image (UfoBufferPrivate *priv)
+{
+    cl_image_desc desc;
+    cl_image_format format;
+    cl_int errcode;
+    cl_mem mem;
+
+    g_assert ((priv->requisition.n_dims == 2) ||
+              (priv->requisition.n_dims == 3));
+
+    if (priv->device_image != NULL)
+        UFO_RESOURCES_CHECK_CLERR (clReleaseMemObject (priv->device_image));
+
+    format.image_channel_order = CL_R;
+    format.image_channel_data_type = CL_FLOAT;
+
+    if (priv->requisition.n_dims == 2) {
+        desc.image_type = CL_MEM_OBJECT_IMAGE2D;
+        desc.image_depth = 1;
+    }
+    else {
+        desc.image_type = CL_MEM_OBJECT_IMAGE3D;
+        desc.image_depth = priv->requisition.dims[2];
+    }
+
+    desc.image_width = priv->requisition.dims[0];
+    desc.image_height = priv->requisition.dims[1];
+    desc.image_array_size = 0;
+    desc.image_row_pitch = 0;
+    desc.image_slice_pitch = 0;
+    desc.num_mip_levels = 0;
+    desc.num_samples = 0;
+    desc.buffer = NULL;
+
+    mem = clCreateImage (priv->context,
+                         CL_MEM_READ_WRITE,
+                         &format, &desc,
+                         NULL, &errcode);
+
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+    priv->device_image = mem;
+}
+#else
+static void
+alloc_device_image (UfoBufferPrivate *priv)
+{
+    cl_image_format format;
+    cl_mem_flags flags;
+    cl_int err;
+    gsize width, height, depth;
+    cl_mem mem = NULL;
+
+    g_assert ((priv->requisition.n_dims == 2) ||
+              (priv->requisition.n_dims == 3));
+
+    if (priv->device_image != NULL)
+        UFO_RESOURCES_CHECK_CLERR (clReleaseMemObject (priv->device_image));
+
+    format.image_channel_order = CL_R;
+    format.image_channel_data_type = CL_FLOAT;
+
+    flags = CL_MEM_READ_WRITE;
+    width = priv->requisition.dims[0];
+    height = priv->requisition.dims[1];
+    depth = priv->requisition.dims[2];
+
+    if (priv->requisition.n_dims == 2) {
+        mem = clCreateImage2D (priv->context,
+                               flags, &format,
+                               width, height, 0,
+                               NULL, &err);
+    }
+    else if (priv->requisition.n_dims == 3) {
+        mem = clCreateImage3D (priv->context,
+                               flags, &format,
+                               width, height, depth, 0, 0,
+                               NULL, &err);
     }
 
-    priv->host_array.data = g_malloc0 (priv->size);
-    priv->device_array = clCreateBuffer (priv->context,
-                                         CL_MEM_READ_WRITE, /* XXX: we _should_ evaluate USE_HOST_PTR */
-                                         priv->size,
-                                         NULL,
-                                         &err);
     UFO_RESOURCES_CHECK_CLERR (err);
-    priv->location = UFO_LOCATION_HOST;
+    g_assert (mem != NULL);
+    priv->device_image = mem;
 }
+#endif
 
 /**
  * ufo_buffer_new:
  * @requisition: (in): size requisition
- * @context: (in): cl_context to use for creating the device array
+ * @context: (in) (allow-none): cl_context to use for creating the device array
  *
  * Create a new #UfoBuffer.
  *
@@ -104,16 +214,45 @@ ufo_buffer_new (UfoRequisition *requisition,
                 gpointer context)
 {
     UfoBuffer *buffer;
+    UfoBufferPrivate *priv;
 
-    g_return_val_if_fail ((requisition->n_dims <= UFO_BUFFER_MAX_NDIMS), NULL);
+    g_return_val_if_fail ((requisition->n_dims <= UFO_BUFFER_MAX_NDIMS) &&
+                          (requisition->n_dims > 0), NULL);
     buffer = UFO_BUFFER (g_object_new (UFO_TYPE_BUFFER, NULL));
-    buffer->priv->context = context;
+    priv = buffer->priv;
+    priv->context = context;
+
+    priv->size = compute_required_size (requisition);
+    copy_requisition (requisition, &priv->requisition);
 
-    alloc_mem (buffer->priv, requisition);
     return buffer;
 }
 
 /**
+ * ufo_buffer_new_with_size:
+ * @dims: (element-type guint64): size requisition
+ * @context: (allow-none): cl_context to use for creating the device array
+ *
+ * Create a new #UfoBuffer with a list of dimensions.
+ *
+ * Return value: A new #UfoBuffer with the given dimensions.
+ */
+UfoBuffer *
+ufo_buffer_new_with_size (GList *dims,
+                          gpointer context)
+{
+    UfoRequisition req;
+
+    req.n_dims = g_list_length (dims);
+    g_assert (req.n_dims < 16);
+
+    for (guint i = 0; i < req.n_dims; i++)
+        req.dims[i] = (gsize) g_list_nth_data (dims, i);
+
+    return ufo_buffer_new (&req, context);
+}
+
+/**
  * ufo_buffer_get_size:
  * @buffer: A #UfoBuffer
  *
@@ -129,29 +268,82 @@ ufo_buffer_get_size (UfoBuffer *buffer)
 }
 
 static void
-copy_host_to_host (UfoBufferPrivate *src_priv,
-                   UfoBufferPrivate *dst_priv)
+set_region_from_requisition (size_t region[3],
+                             UfoRequisition *requisition)
 {
-    g_memmove (dst_priv->host_array.data,
-               src_priv->host_array.data,
+    region[0] = requisition->dims[0];
+    region[1] = requisition->dims[1];
+
+    if (requisition->n_dims == 3)
+        region[2] = requisition->dims[2];
+    else
+        region[2] = 1;
+}
+
+static void
+transfer_host_to_host (UfoBufferPrivate *src_priv,
+                       UfoBufferPrivate *dst_priv,
+                       cl_command_queue queue)
+{
+    g_memmove (dst_priv->host_array,
+               src_priv->host_array,
                src_priv->size);
 }
 
 static void
-copy_device_to_device (UfoBufferPrivate *src_priv,
-                       UfoBufferPrivate *dst_priv)
+transfer_host_to_device (UfoBufferPrivate *src_priv,
+                         UfoBufferPrivate *dst_priv,
+                         cl_command_queue queue)
 {
-    cl_event event;
     cl_int errcode;
-    cl_command_queue cmd_queue;
 
-    cmd_queue = src_priv->last_queue != NULL ? src_priv->last_queue : dst_priv->last_queue;
-    g_assert (cmd_queue != NULL);
+    errcode = clEnqueueWriteBuffer (queue,
+                                    dst_priv->device_array,
+                                    CL_TRUE,
+                                    0, src_priv->size,
+                                    src_priv->host_array,
+                                    0, NULL, NULL);
 
-    errcode = clEnqueueCopyBuffer (cmd_queue,
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+}
+
+static void
+transfer_host_to_image (UfoBufferPrivate *src_priv,
+                        UfoBufferPrivate *dst_priv,
+                        cl_command_queue queue)
+{
+    cl_int errcode;
+    cl_event event;
+    size_t region[3];
+    size_t origin[] = { 0, 0, 0 };
+
+    set_region_from_requisition (region, &src_priv->requisition);
+
+    errcode = clEnqueueWriteImage (queue,
+                                   dst_priv->device_image,
+                                   CL_TRUE,
+                                   origin, region,
+                                   0, 0,
+                                   src_priv->host_array,
+                                   0, NULL, &event);
+
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+    UFO_RESOURCES_CHECK_CLERR (clWaitForEvents (1, &event));
+    UFO_RESOURCES_CHECK_CLERR (clReleaseEvent (event));
+}
+
+static void
+transfer_device_to_device (UfoBufferPrivate *src_priv,
+                           UfoBufferPrivate *dst_priv,
+                           cl_command_queue queue)
+{
+    cl_event event;
+    cl_int errcode;
+
+    errcode = clEnqueueCopyBuffer (queue,
                                    src_priv->device_array,
                                    dst_priv->device_array,
-                                   0, 0,                      /* offsets */
+                                   0, 0,
                                    src_priv->size,
                                    0, NULL, &event);
 
@@ -161,60 +353,114 @@ copy_device_to_device (UfoBufferPrivate *src_priv,
 }
 
 static void
-ufo_buffer_to_host (UfoBuffer *buffer, gpointer cmd_queue)
+transfer_device_to_host (UfoBufferPrivate *src_priv,
+                         UfoBufferPrivate *dst_priv,
+                         cl_command_queue queue)
 {
-    UfoBufferPrivate *priv;
-    cl_int cl_err;
-    cl_command_queue queue;
+    cl_int errcode;
 
-    g_return_if_fail (UFO_IS_BUFFER (buffer));
-    priv = buffer->priv;
+    errcode = clEnqueueReadBuffer (queue,
+                                   src_priv->device_array,
+                                   CL_TRUE,
+                                   0, src_priv->size,
+                                   dst_priv->host_array,
+                                   0, NULL, NULL);
 
-    queue = cmd_queue == NULL ? priv->last_queue : cmd_queue;
-    priv->last_queue = cmd_queue;
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+}
 
-    if (priv->location == UFO_LOCATION_HOST)
-        return;
+static void
+transfer_device_to_image (UfoBufferPrivate *src_priv,
+                          UfoBufferPrivate *dst_priv,
+                          cl_command_queue queue)
+{
+    cl_event event;
+    cl_int errcode;
+    size_t region[3];
+    size_t origin[] = { 0, 0, 0 };
 
-    cl_err = clEnqueueReadBuffer (queue,
-                                  priv->device_array,
-                                  CL_TRUE,
-                                  0, priv->size,
-                                  priv->host_array.data,
-                                  0, NULL, NULL);
+    set_region_from_requisition (region, &src_priv->requisition);
+
+    errcode = clEnqueueCopyBufferToImage (queue,
+                                          src_priv->device_array,
+                                          dst_priv->device_image,
+                                          0, origin, region,
+                                          0, NULL, &event);
 
-    priv->location = UFO_LOCATION_HOST;
-    UFO_RESOURCES_CHECK_CLERR (cl_err);
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+    UFO_RESOURCES_CHECK_CLERR (clWaitForEvents (1, &event));
+    UFO_RESOURCES_CHECK_CLERR (clReleaseEvent (event));
 }
 
 static void
-ufo_buffer_to_device (UfoBuffer *buffer, gpointer cmd_queue)
+transfer_image_to_image (UfoBufferPrivate *src_priv,
+                         UfoBufferPrivate *dst_priv,
+                         cl_command_queue queue)
 {
-    UfoBufferPrivate *priv;
-    cl_int cl_err;
-    cl_command_queue queue;
+    cl_event event;
+    cl_int errcode;
+    size_t region[3];
+    size_t origin[] = { 0, 0, 0 };
 
-    g_return_if_fail (UFO_IS_BUFFER (buffer));
-    priv = buffer->priv;
+    set_region_from_requisition (region, &src_priv->requisition);
 
-    queue = cmd_queue == NULL ? priv->last_queue : cmd_queue;
-    priv->last_queue = cmd_queue;
-    g_assert (cmd_queue);
+    errcode = clEnqueueCopyImage (queue,
+                                  src_priv->device_image,
+                                  dst_priv->device_image,
+                                  origin, origin, region,
+                                  0, NULL, &event);
 
-    if (priv->location == UFO_LOCATION_DEVICE)
-        return;
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+    UFO_RESOURCES_CHECK_CLERR (clWaitForEvents (1, &event));
+    UFO_RESOURCES_CHECK_CLERR (clReleaseEvent (event));
+}
 
-    cl_err = clEnqueueWriteBuffer ((cl_command_queue) queue,
-                                   priv->device_array,
-                                   CL_TRUE,
-                                   0, priv->size,
-                                   priv->host_array.data,
-                                   0, NULL, NULL);
+static void
+transfer_image_to_host (UfoBufferPrivate *src_priv,
+                        UfoBufferPrivate *dst_priv,
+                        cl_command_queue queue)
+{
+    cl_int errcode;
+    size_t region[3];
+    size_t origin[] = { 0, 0, 0 };
+
+    set_region_from_requisition (region, &src_priv->requisition);
+
+    errcode = clEnqueueReadImage (queue,
+                                  src_priv->device_image,
+                                  CL_TRUE,
+                                  origin, region,
+                                  0, 0,
+                                  dst_priv->host_array,
+                                  0, NULL, NULL);
 
-    priv->location = UFO_LOCATION_DEVICE;
-    UFO_RESOURCES_CHECK_CLERR (cl_err);
+    UFO_RESOURCES_CHECK_CLERR (errcode);
 }
 
+static void
+transfer_image_to_device (UfoBufferPrivate *src_priv,
+                          UfoBufferPrivate *dst_priv,
+                          cl_command_queue queue)
+{
+    cl_event event;
+    cl_int errcode;
+    size_t region[3];
+    size_t origin[] = { 0, 0, 0 };
+
+    set_region_from_requisition (region, &src_priv->requisition);
+
+    errcode = clEnqueueCopyImageToBuffer (queue,
+                                          src_priv->device_image,
+                                          dst_priv->device_array,
+                                          origin, region, 0,
+                                          0, NULL, &event);
+
+    UFO_RESOURCES_CHECK_CLERR (errcode);
+    UFO_RESOURCES_CHECK_CLERR (clWaitForEvents (1, &event));
+    UFO_RESOURCES_CHECK_CLERR (clReleaseEvent (event));
+}
+
+
 /**
  * ufo_buffer_copy:
  * @src: Source #UfoBuffer
@@ -226,41 +472,40 @@ ufo_buffer_to_device (UfoBuffer *buffer, gpointer cmd_queue)
 void
 ufo_buffer_copy (UfoBuffer *src, UfoBuffer *dst)
 {
+    typedef void (*TransferFunc) (UfoBufferPrivate *, UfoBufferPrivate *, cl_command_queue);
+    typedef void (*AllocFunc) (UfoBufferPrivate *priv);
+
     UfoBufferPrivate *spriv;
     UfoBufferPrivate *dpriv;
+    cl_command_queue queue;
+
+    TransferFunc transfer[3][3] = {
+        { transfer_host_to_host, transfer_host_to_device, transfer_host_to_image },
+        { transfer_device_to_host, transfer_device_to_device, transfer_device_to_image },
+        { transfer_image_to_host, transfer_image_to_device, transfer_image_to_image }
+    };
+
+    AllocFunc alloc[3] = { alloc_host_mem, alloc_device_array, alloc_device_image };
 
     g_return_if_fail (UFO_IS_BUFFER (src) && UFO_IS_BUFFER (dst));
     g_return_if_fail (src->priv->size == dst->priv->size);
 
     spriv = src->priv;
     dpriv = dst->priv;
+    queue = spriv->last_queue != NULL ? spriv->last_queue : dpriv->last_queue;
 
-    if (spriv->location == dpriv->location) {
-        switch (spriv->location) {
-            case UFO_LOCATION_HOST:
-                copy_host_to_host (spriv, dpriv);
-                break;
-            case UFO_LOCATION_DEVICE:
-                copy_device_to_device (spriv, dpriv);
-                break;
-            default:
-                g_warning ("oops, we should not copy invalid data");
-        }
+    if (spriv->location == UFO_LOCATION_INVALID) {
+        alloc_host_mem (spriv);
+        spriv->location = UFO_LOCATION_HOST;
     }
-    else {
-        cl_command_queue cmd_queue;
-
-        cmd_queue = spriv->last_queue != NULL ? spriv->last_queue : dpriv->last_queue;
-
-        if (cmd_queue == NULL || dpriv->location == UFO_LOCATION_HOST) {
-            ufo_buffer_to_host (src, cmd_queue);
-            copy_host_to_host (spriv, dpriv);
-        }
-        else {
-            ufo_buffer_to_device (src, cmd_queue);
-            copy_device_to_device (spriv, dpriv);
-        }
+
+    if (dpriv->location == UFO_LOCATION_INVALID) {
+        alloc[spriv->location](dpriv);
+        dpriv->location = spriv->location;
     }
+
+    transfer[spriv->location][dpriv->location](spriv, dpriv, queue);
+    dpriv->last_queue = queue;
 }
 
 /**
@@ -303,9 +548,9 @@ ufo_buffer_resize (UfoBuffer *buffer,
 
     priv = UFO_BUFFER_GET_PRIVATE (buffer);
 
-    if (priv->host_array.data != NULL) {
-        g_free (priv->host_array.data);
-        priv->host_array.data = NULL;
+    if (priv->host_array != NULL) {
+        g_free (priv->host_array);
+        priv->host_array = NULL;
     }
 
     if (priv->device_array != NULL) {
@@ -313,7 +558,7 @@ ufo_buffer_resize (UfoBuffer *buffer,
         priv->device_array = NULL;
     }
 
-    alloc_mem (priv, requisition);
+    copy_requisition (requisition, &priv->requisition);
 }
 
 /**
@@ -329,14 +574,17 @@ gint
 ufo_buffer_cmp_dimensions (UfoBuffer *buffer,
                            UfoRequisition *requisition)
 {
+    UfoBufferPrivate *priv;
     gint result;
+
     g_return_val_if_fail (UFO_IS_BUFFER(buffer), FALSE);
 
+    priv = buffer->priv;
     result = 0;
 
-    for (guint i = 0; i < buffer->priv->host_array.num_dims; i++) {
+    for (guint i = 0; i < priv->requisition.n_dims; i++) {
         gint req_dim = (gint) requisition->dims[i];
-        gint host_dim = (gint) buffer->priv->host_array.dim_size[i];
+        gint host_dim = (gint) priv->requisition.dims[i];
         result += req_dim - host_dim;
     }
 
@@ -358,10 +606,24 @@ ufo_buffer_get_requisition (UfoBuffer *buffer,
 
     g_return_if_fail (UFO_IS_BUFFER (buffer) && (requisition != NULL));
     priv = buffer->priv;
-    requisition->n_dims = priv->host_array.num_dims;
 
-    for (guint i = 0; i < priv->host_array.num_dims; i++)
-        requisition->dims[i] = priv->host_array.dim_size[i];
+    copy_requisition (&priv->requisition, requisition);
+}
+
+static void
+update_last_queue (UfoBufferPrivate *priv,
+                   cl_command_queue queue)
+{
+    if (queue != NULL)
+        priv->last_queue = queue;
+}
+
+static void
+update_location (UfoBufferPrivate *priv,
+                 UfoMemLocation new_location)
+{
+    priv->last_location = priv->location;
+    priv->location = new_location;
 }
 
 /**
@@ -376,9 +638,25 @@ ufo_buffer_get_requisition (UfoBuffer *buffer,
 gfloat *
 ufo_buffer_get_host_array (UfoBuffer *buffer, gpointer cmd_queue)
 {
+    UfoBufferPrivate *priv;
+
     g_return_val_if_fail (UFO_IS_BUFFER (buffer), NULL);
-    ufo_buffer_to_host (buffer, cmd_queue);
-    return buffer->priv->host_array.data;
+    priv = buffer->priv;
+
+    update_last_queue (priv, cmd_queue);
+
+    if (priv->host_array == NULL)
+        alloc_host_mem (priv);
+
+    if (priv->location == UFO_LOCATION_DEVICE && priv->device_array)
+        transfer_device_to_host (priv, priv, priv->last_queue);
+
+    if (priv->location == UFO_LOCATION_DEVICE_IMAGE && priv->device_image)
+        transfer_image_to_host (priv, priv, priv->last_queue);
+
+    update_location (priv, UFO_LOCATION_HOST);
+
+    return priv->host_array;
 }
 
 /**
@@ -395,59 +673,100 @@ ufo_buffer_get_host_array (UfoBuffer *buffer, gpointer cmd_queue)
 gpointer
 ufo_buffer_get_device_array (UfoBuffer *buffer, gpointer cmd_queue)
 {
+    UfoBufferPrivate *priv;
+
     g_return_val_if_fail (UFO_IS_BUFFER (buffer), NULL);
-    ufo_buffer_to_device (buffer, cmd_queue);
-    return buffer->priv->device_array;
+    priv = buffer->priv;
+
+    update_last_queue (priv, cmd_queue);
+
+    if (priv->device_array == NULL)
+        alloc_device_array (priv);
+
+    if (priv->location == UFO_LOCATION_HOST && priv->host_array)
+        transfer_host_to_device (priv, priv, priv->last_queue);
+
+    if (priv->location == UFO_LOCATION_DEVICE_IMAGE && priv->device_array)
+        transfer_image_to_device (priv, priv, priv->last_queue);
+
+    update_location (priv, UFO_LOCATION_DEVICE);
+
+    return priv->device_array;
 }
 
 /**
- * ufo_buffer_discard_location:
- * @buffer: A #UfoBuffer
- * @location: Location to discard
+ * ufo_buffer_get_device_image:
+ * @buffer: A #UfoBuffer.
+ * @cmd_queue: (allow-none): A cl_command_queue object or %NULL.
  *
- * Discard @location and use "other" location without copying to it first.
+ * Return the current cl_mem image object of @buffer. If the data is not yet in
+ * device memory, it is transfered via @cmd_queue to the object. If @cmd_queue
+ * is %NULL @cmd_queue, the last used command queue is used.
+ *
+ * Returns: (transfer none): A cl_mem image object associated with @buffer.
  */
-void
-ufo_buffer_discard_location (UfoBuffer *buffer,
-                             UfoMemLocation location)
+gpointer
+ufo_buffer_get_device_image (UfoBuffer *buffer,
+                             gpointer cmd_queue)
 {
-    g_return_if_fail (UFO_IS_BUFFER (buffer));
-    buffer->priv->location = location == UFO_LOCATION_HOST ? UFO_LOCATION_DEVICE : UFO_LOCATION_HOST;
+    UfoBufferPrivate *priv;
+
+    g_return_val_if_fail (UFO_IS_BUFFER (buffer), NULL);
+    priv = buffer->priv;
+
+    update_last_queue (priv, cmd_queue);
+
+    if (priv->device_image == NULL)
+        alloc_device_image (priv);
+
+    if (priv->location == UFO_LOCATION_HOST && priv->host_array)
+        transfer_host_to_image (priv, priv, priv->last_queue);
+
+    if (priv->location == UFO_LOCATION_DEVICE && priv->device_array)
+        transfer_device_to_image (priv, priv, priv->last_queue);
+
+    update_location (priv, UFO_LOCATION_DEVICE_IMAGE);
+
+    return priv->device_image;
 }
 
 /**
- * ufo_buffer_convert:
+ * ufo_buffer_discard_location:
  * @buffer: A #UfoBuffer
- * @depth: Source bit depth of host data
  *
- * Convert host data according to its @depth to the internal 32-bit floating
- * point representation.
+ * Discard the current and use the last location without copying to it first.
  */
 void
-ufo_buffer_convert (UfoBuffer *buffer,
-                    UfoBufferDepth depth)
+ufo_buffer_discard_location (UfoBuffer *buffer)
+{
+    g_return_if_fail (UFO_IS_BUFFER (buffer));
+
+    buffer->priv->location = buffer->priv->last_location;
+}
+
+static void
+convert_data (UfoBufferPrivate *priv,
+              gconstpointer data,
+              UfoBufferDepth depth)
 {
-    UfoBufferPrivate *priv;
     gint n_pixels;
     gfloat *dst;
 
-    g_return_if_fail (UFO_IS_BUFFER (buffer));
-    priv = buffer->priv;
     n_pixels = (gint) (priv->size / 4);
-    dst = priv->host_array.data;
+    dst = priv->host_array;
 
     /* To save a memory allocation and several copies, we process data from back
      * to front. This is possible if src bit depth is at most half as wide as
      * the 32-bit target buffer. The processor cache should not be a
      * problem. */
     if (depth == UFO_BUFFER_DEPTH_8U) {
-        guint8 *src = (guint8 *) priv->host_array.data;
+        const guint8 *src = (const guint8 *) data;
 
         for (gint i = (n_pixels - 1); i >= 0; i--)
             dst[i] = ((gfloat) src[i]);
     }
     else if (depth == UFO_BUFFER_DEPTH_16U) {
-        guint16 *src = (guint16 *) priv->host_array.data;
+        const guint16 *src = (const guint16 *) data;
 
         for (gint i = (n_pixels - 1); i >= 0; i--)
             dst[i] = ((gfloat) src[i]);
@@ -455,6 +774,56 @@ ufo_buffer_convert (UfoBuffer *buffer,
 }
 
 /**
+ * ufo_buffer_convert:
+ * @buffer: A #UfoBuffer
+ * @depth: Source bit depth of host data
+ *
+ * Convert host data according to its @depth to the internal 32-bit floating
+ * point representation.
+ *
+ * Deprecated: 0.4: Use ufo_buffer_convert_from_data() instead.
+ */
+void
+ufo_buffer_convert (UfoBuffer *buffer,
+                    UfoBufferDepth depth)
+{
+    UfoBufferPrivate *priv;
+
+    g_return_if_fail (UFO_IS_BUFFER (buffer));
+    priv = buffer->priv;
+
+    if (priv->host_array != NULL)
+        convert_data (priv, priv->host_array, depth);
+}
+
+/**
+ * ufo_buffer_convert_from_data:
+ * @buffer: A #UfoBuffer
+ * @data: Pointer to data that should be converted
+ * @depth: Source bit depth of host data
+ *
+ * Convert @data according from @depth to the internal 32-bit floating
+ * point representation.
+ *
+ * Note: @data must provide as many bytes as the buffer was initialized with.
+ */
+void
+ufo_buffer_convert_from_data (UfoBuffer *buffer,
+                              gconstpointer data,
+                              UfoBufferDepth depth)
+{
+    UfoBufferPrivate *priv;
+
+    g_return_if_fail (UFO_IS_BUFFER (buffer));
+    priv = buffer->priv;
+
+    if (priv->host_array == NULL)
+        alloc_host_mem (priv);
+
+    convert_data (priv, data, depth);
+}
+
+/**
  * ufo_buffer_param_spec:
  * @name: canonical name of the property specified
  * @nick: nick name for the property specified
@@ -481,23 +850,27 @@ ufo_buffer_param_spec(const gchar *name, const gchar *nick, const gchar *blurb,
 }
 
 static void
+free_cl_mem (cl_mem *mem)
+{
+    g_assert (mem != NULL);
+
+    if (*mem != NULL) {
+        UFO_RESOURCES_CHECK_CLERR (clReleaseMemObject (*mem));
+        *mem = NULL;
+    }
+}
+
+static void
 ufo_buffer_finalize (GObject *gobject)
 {
     UfoBuffer *buffer = UFO_BUFFER (gobject);
     UfoBufferPrivate *priv = UFO_BUFFER_GET_PRIVATE (buffer);
 
-    g_free (priv->host_array.data);
-    priv->host_array.data = NULL;
-
-    if (priv->device_array != NULL) {
-        UFO_RESOURCES_CHECK_CLERR (clReleaseMemObject (priv->device_array));
-        priv->device_array = NULL;
-    }
+    g_free (priv->host_array);
+    priv->host_array = NULL;
 
-    if (priv->timer != NULL) {
-        g_timer_destroy (priv->timer);
-        priv->timer = NULL;
-    }
+    free_cl_mem (&priv->device_array);
+    free_cl_mem (&priv->device_image);
 
     G_OBJECT_CLASS(ufo_buffer_parent_class)->finalize(gobject);
 }
@@ -518,10 +891,12 @@ ufo_buffer_init (UfoBuffer *buffer)
     buffer->priv = priv = UFO_BUFFER_GET_PRIVATE(buffer);
     priv->last_queue = NULL;
     priv->device_array = NULL;
-    priv->host_array.data = NULL;
-    priv->host_array.num_dims = 0;
-    priv->timer = g_timer_new ();
-    g_timer_stop (priv->timer);
+    priv->device_image = NULL;
+    priv->host_array = NULL;
+
+    priv->location = UFO_LOCATION_INVALID;
+    priv->last_location = UFO_LOCATION_INVALID;
+    priv->requisition.n_dims = 0;
 }
 
 static void
diff --git a/ufo/ufo-buffer.h b/ufo/ufo-buffer.h
index de6c0f6..0dc56e8 100644
--- a/ufo/ufo-buffer.h
+++ b/ufo/ufo-buffer.h
@@ -39,20 +39,6 @@ G_BEGIN_DECLS
 #define UFO_IS_PARAM_SPEC_BUFFER(pspec)  (G_TYPE_CHECK_INSTANCE_TYPE((pspec), UFO_TYPE_PARAM_BUFFER))
 #define UFO_BUFFER_PARAM_SPEC(pspec)     (G_TYPE_CHECK_INSTANCE_CAST((pspec), UFO_TYPE_PARAM_BUFFER, UfoBufferParamSpec))
 
-/**
- * UfoMemLocation:
- * @UFO_LOCATION_INVALID: Memory is neither valid on host nor on device.
- * @UFO_LOCATION_HOST: Memory is valid on host memory.
- * @UFO_LOCATION_DEVICE: Memory is valid on device memory.
- *
- * Memory locations of a #UfoBuffer.
- */
-typedef enum {
-    UFO_LOCATION_INVALID,
-    UFO_LOCATION_HOST,
-    UFO_LOCATION_DEVICE
-} UfoMemLocation;
-
 typedef struct _UfoBuffer           UfoBuffer;
 typedef struct _UfoBufferClass      UfoBufferClass;
 typedef struct _UfoBufferPrivate    UfoBufferPrivate;
@@ -129,6 +115,8 @@ typedef enum {
 
 UfoBuffer*  ufo_buffer_new                  (UfoRequisition *requisition,
                                              gpointer        context);
+UfoBuffer*  ufo_buffer_new_with_size        (GList          *dims,
+                                             gpointer        context);
 void        ufo_buffer_resize               (UfoBuffer      *buffer,
                                              UfoRequisition *requisition);
 gint        ufo_buffer_cmp_dimensions       (UfoBuffer      *buffer,
@@ -143,10 +131,14 @@ gfloat*     ufo_buffer_get_host_array       (UfoBuffer      *buffer,
                                              gpointer        cmd_queue);
 gpointer    ufo_buffer_get_device_array     (UfoBuffer      *buffer,
                                              gpointer        cmd_queue);
-void        ufo_buffer_discard_location     (UfoBuffer      *buffer,
-                                             UfoMemLocation  location);
+gpointer    ufo_buffer_get_device_image     (UfoBuffer      *buffer,
+                                             gpointer        cmd_queue);
+void        ufo_buffer_discard_location     (UfoBuffer      *buffer);
 void        ufo_buffer_convert              (UfoBuffer      *buffer,
                                              UfoBufferDepth  depth);
+void        ufo_buffer_convert_from_data    (UfoBuffer      *buffer,
+                                             gconstpointer   data,
+                                             UfoBufferDepth  depth);
 GType       ufo_buffer_get_type             (void);
 
 GParamSpec* ufo_buffer_param_spec           (const gchar*   name,
diff --git a/ufo/ufo-config.c b/ufo/ufo-config.c
index afebe19..aad6a7b 100644
--- a/ufo/ufo-config.c
+++ b/ufo/ufo-config.c
@@ -42,18 +42,16 @@ static void add_path (const gchar *path, UfoConfigPrivate *priv);
 enum {
     PROP_0,
     PROP_PATHS,
-    PROP_PROFILE_LEVEL,
-    PROP_PROFILE_OUTPUT_PREFIX,
+    PROP_DEVICE_TYPE,
     N_PROPERTIES
 };
 
 struct _UfoConfigPrivate {
-    GValueArray         *path_array;
-    UfoProfilerLevel     profile_level;
-    gchar               *profile_output_prefix;
+    GValueArray     *path_array;
+    UfoDeviceType    device_type;
 };
 
-static GParamSpec *config_properties[N_PROPERTIES] = { NULL, };
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
 
 /**
  * ufo_config_new:
@@ -98,6 +96,13 @@ ufo_config_get_paths (UfoConfig *config)
     return paths;
 }
 
+UfoDeviceType
+ufo_config_get_device_type (UfoConfig *config)
+{
+    g_return_val_if_fail (UFO_IS_CONFIG (config), 0);
+    return config->priv->device_type;
+}
+
 /**
  * ufo_config_add_paths:
  * @config: A #UfoConfig object
@@ -136,25 +141,21 @@ ufo_config_set_property (GObject      *object,
     switch (property_id) {
         case PROP_PATHS:
             {
-                GValueArray *array;
+                GValueArray *more_paths;
 
-                if (priv->path_array != NULL)
-                    g_value_array_free (priv->path_array);
+                more_paths = g_value_get_boxed (value);
 
-                array = g_value_get_boxed (value);
-
-                if (array != NULL)
-                    priv->path_array = g_value_array_copy (array);
+                if (more_paths != NULL) {
+                    for (guint i = 0; i < more_paths->n_values; i++) {
+                        g_value_array_append (priv->path_array,
+                                              g_value_array_get_nth (more_paths, i));
+                    }
+                }
             }
             break;
 
-        case PROP_PROFILE_LEVEL:
-            priv->profile_level = g_value_get_flags (value);
-            break;
-
-        case PROP_PROFILE_OUTPUT_PREFIX:
-            g_free (priv->profile_output_prefix);
-            priv->profile_output_prefix = g_strdup (g_value_get_string (value));
+        case PROP_DEVICE_TYPE:
+            priv->device_type = g_value_get_flags (value);
             break;
 
         default:
@@ -176,12 +177,8 @@ ufo_config_get_property (GObject      *object,
             g_value_set_boxed (value, priv->path_array);
             break;
 
-        case PROP_PROFILE_LEVEL:
-            g_value_set_flags (value, priv->profile_level);
-            break;
-
-        case PROP_PROFILE_OUTPUT_PREFIX:
-            g_value_set_string (value, priv->profile_output_prefix);
+        case PROP_DEVICE_TYPE:
+            g_value_set_flags (value, priv->device_type);
             break;
 
         default:
@@ -211,11 +208,11 @@ ufo_config_finalize (GObject *object)
 static void
 ufo_config_class_init (UfoConfigClass *klass)
 {
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-    gobject_class->set_property = ufo_config_set_property;
-    gobject_class->get_property = ufo_config_get_property;
-    gobject_class->dispose      = ufo_config_dispose;
-    gobject_class->finalize     = ufo_config_finalize;
+    GObjectClass *oclass = G_OBJECT_CLASS (klass);
+    oclass->set_property = ufo_config_set_property;
+    oclass->get_property = ufo_config_get_property;
+    oclass->dispose      = ufo_config_dispose;
+    oclass->finalize     = ufo_config_finalize;
 
     /**
      * UfoConfig:paths:
@@ -223,7 +220,7 @@ ufo_config_class_init (UfoConfigClass *klass)
      * An array of strings with paths pointing to possible filter and kernel
      * file locations.
      */
-    config_properties[PROP_PATHS] =
+    properties[PROP_PATHS] =
         g_param_spec_value_array ("paths",
                                   "Array with paths",
                                   "Array with paths",
@@ -235,30 +232,24 @@ ufo_config_class_init (UfoConfigClass *klass)
                                   G_PARAM_READWRITE);
 
     /**
-     * UfoConfig:profile-level:
+     * UfoConfig:device-class:
      *
-     * Controls the amount of profiling.
+     * Let the user select which device class to use for execution.
      *
-     * See: #UfoProfilerLevel for different levels of profiling.
+     * See: #UfoDeviceType for the device classes.
      */
-    config_properties[PROP_PROFILE_LEVEL] =
-        g_param_spec_flags ("profile-level",
-                            "Profiling level",
-                            "Profiling level",
-                            UFO_TYPE_PROFILER_LEVEL,
-                            UFO_PROFILER_LEVEL_NONE,
+    properties[PROP_DEVICE_TYPE] =
+        g_param_spec_flags ("device-type",
+                            "Device type to use",
+                            "Device type to use",
+                            UFO_TYPE_DEVICE_TYPE,
+                            UFO_DEVICE_ALL,
                             G_PARAM_READWRITE);
 
-    config_properties[PROP_PROFILE_OUTPUT_PREFIX] =
-        g_param_spec_string ("profile-output-prefix",
-                             "Filename prefix for profiling output",
-                             "Filename prefix for profiling output. If NULL, information is output to stdout.",
-                             NULL,
-                             G_PARAM_READWRITE);
-
-    g_object_class_install_property (gobject_class, PROP_PATHS, config_properties[PROP_PATHS]);
-    g_object_class_install_property (gobject_class, PROP_PROFILE_LEVEL, config_properties[PROP_PROFILE_LEVEL]);
-    g_object_class_install_property (gobject_class, PROP_PROFILE_OUTPUT_PREFIX, config_properties[PROP_PROFILE_OUTPUT_PREFIX]);
+    g_object_class_install_property (oclass, PROP_PATHS,
+                                     properties[PROP_PATHS]);
+    g_object_class_install_property (oclass, PROP_DEVICE_TYPE,
+                                     properties[PROP_DEVICE_TYPE]);
 
     g_type_class_add_private(klass, sizeof (UfoConfigPrivate));
 }
@@ -270,8 +261,7 @@ ufo_config_init (UfoConfig *config)
 
     config->priv = priv = UFO_CONFIG_GET_PRIVATE (config);
     priv->path_array = g_value_array_new (0);
-    priv->profile_level = UFO_PROFILER_LEVEL_NONE;
-    priv->profile_output_prefix = NULL;
+    priv->device_type = UFO_DEVICE_ALL;
 
     add_path ("/usr/local/lib64/ufo", priv);
     add_path ("/usr/local/lib/ufo", priv);
diff --git a/ufo/ufo-config.h b/ufo/ufo-config.h
index 447350e..87b35ea 100644
--- a/ufo/ufo-config.h
+++ b/ufo/ufo-config.h
@@ -61,11 +61,27 @@ struct _UfoConfigClass {
     GObjectClass parent_class;
 };
 
-UfoConfig   * ufo_config_new        (void);
-void          ufo_config_add_paths  (UfoConfig  *config,
-                                     GList      *paths);
-GList       * ufo_config_get_paths  (UfoConfig  *config);
-GType         ufo_config_get_type   (void);
+/**
+ * UfoDeviceType:
+ * @UFO_DEVICE_ALL: All devices
+ * @UFO_DEVICE_CPU: Only CPU devices
+ * @UFO_DEVICE_GPU: Only GPU devices
+ *
+ * Types of OpenCL devices to query for. See UfoConfig:"device-type".
+ */
+typedef enum {
+    UFO_DEVICE_CPU = 1 << 0,
+    UFO_DEVICE_GPU = 1 << 1,
+    UFO_DEVICE_ALL = (1 << 1) | (1 << 0)
+} UfoDeviceType;
+
+
+UfoConfig   * ufo_config_new                (void);
+void          ufo_config_add_paths          (UfoConfig *config,
+                                             GList     *paths);
+GList       * ufo_config_get_paths          (UfoConfig *config);
+UfoDeviceType ufo_config_get_device_type    (UfoConfig *config);
+GType         ufo_config_get_type           (void);
 
 G_END_DECLS
 
diff --git a/ufo/ufo-cpu-task-iface.c b/ufo/ufo-cpu-task-iface.c
index fe85176..28b406c 100644
--- a/ufo/ufo-cpu-task-iface.c
+++ b/ufo/ufo-cpu-task-iface.c
@@ -33,14 +33,6 @@ ufo_cpu_task_process (UfoCpuTask *task,
     return UFO_CPU_TASK_GET_IFACE (task)->process (task, inputs, output, requisition);
 }
 
-void
-ufo_cpu_task_reduce (UfoCpuTask *task,
-                     UfoBuffer *output,
-                     UfoRequisition *requisition)
-{
-    UFO_CPU_TASK_GET_IFACE (task)->reduce (task, output, requisition);
-}
-
 gboolean
 ufo_cpu_task_generate (UfoCpuTask *task,
                        UfoBuffer *output,
@@ -59,14 +51,6 @@ ufo_cpu_task_process_real (UfoCpuTask *task,
     return FALSE;
 }
 
-static void
-ufo_cpu_task_reduce_real (UfoCpuTask *task,
-                          UfoBuffer *output,
-                          UfoRequisition *requisition)
-{
-    g_warning ("`reduce' of UfoCpuTaskInterface not implemented");
-}
-
 static gboolean
 ufo_cpu_task_generate_real (UfoCpuTask *task,
                             UfoBuffer *output,
@@ -80,6 +64,5 @@ static void
 ufo_cpu_task_default_init (UfoCpuTaskInterface *iface)
 {
     iface->process = ufo_cpu_task_process_real;
-    iface->reduce = ufo_cpu_task_reduce_real;
     iface->generate = ufo_cpu_task_generate_real;
 }
diff --git a/ufo/ufo-cpu-task-iface.h b/ufo/ufo-cpu-task-iface.h
index 5cc2e64..a774e63 100644
--- a/ufo/ufo-cpu-task-iface.h
+++ b/ufo/ufo-cpu-task-iface.h
@@ -43,28 +43,22 @@ struct _UfoCpuTaskIface {
     /*< private >*/
     UfoTaskIface parent_iface;
 
-    gboolean (*process) (UfoCpuTask *task,
-                         UfoBuffer **inputs,
-                         UfoBuffer *output,
-                         UfoRequisition *requisition);
-    void     (*reduce)  (UfoCpuTask *task,
-                         UfoBuffer *output,
-                         UfoRequisition *requisition);
-    gboolean (*generate)(UfoCpuTask *task,
-                         UfoBuffer *output,
-                         UfoRequisition *requisition);
+    gboolean (*process)  (UfoCpuTask *task,
+                          UfoBuffer **inputs,
+                          UfoBuffer *output,
+                          UfoRequisition *requisition);
+    gboolean (*generate) (UfoCpuTask *task,
+                          UfoBuffer *output,
+                          UfoRequisition *requisition);
 };
 
-gboolean    ufo_cpu_task_process (UfoCpuTask     *task,
-                                  UfoBuffer     **inputs,
-                                  UfoBuffer      *output,
-                                  UfoRequisition *requisition);
-void        ufo_cpu_task_reduce  (UfoCpuTask     *task,
-                                  UfoBuffer      *output,
-                                  UfoRequisition *requisition);
-gboolean    ufo_cpu_task_generate(UfoCpuTask     *task,
-                                  UfoBuffer      *output,
-                                  UfoRequisition *requisition);
+gboolean    ufo_cpu_task_process    (UfoCpuTask     *task,
+                                     UfoBuffer     **inputs,
+                                     UfoBuffer      *output,
+                                     UfoRequisition *requisition);
+gboolean    ufo_cpu_task_generate   (UfoCpuTask     *task,
+                                     UfoBuffer      *output,
+                                     UfoRequisition *requisition);
 
 GType ufo_cpu_task_get_type (void);
 
diff --git a/ufo/ufo-daemon.c b/ufo/ufo-daemon.c
new file mode 100644
index 0000000..eec9ce3
--- /dev/null
+++ b/ufo/ufo-daemon.c
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef __APPLE__
+#include <OpenCL/cl.h>
+#else
+#include <CL/cl.h>
+#endif
+
+#ifdef MPI
+#include <mpi.h>
+#include <ufo/ufo-mpi-messenger.h>
+#endif
+
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ufo/ufo-config.h>
+#include <ufo/ufo-daemon.h>
+#include <ufo/ufo-dummy-task.h>
+#include <ufo/ufo-input-task.h>
+#include <ufo/ufo-output-task.h>
+#include <ufo/ufo-plugin-manager.h>
+#include <ufo/ufo-scheduler.h>
+#include <ufo/ufo-task-graph.h>
+#include <ufo/ufo-zmq-messenger.h>
+#include <ufo/ufo-messenger-iface.h>
+
+G_DEFINE_TYPE (UfoDaemon, ufo_daemon, G_TYPE_OBJECT)
+
+#define UFO_DAEMON_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_DAEMON, UfoDaemonPrivate))
+
+struct _UfoDaemonPrivate {
+    UfoConfig *config;
+    UfoPluginManager *manager;
+    UfoTaskGraph *task_graph;
+    UfoScheduler *scheduler;
+    GThread *scheduler_thread;
+    gpointer socket;
+    UfoNode *input_task;
+    UfoNode *output_task;
+    UfoBuffer *input;
+    gpointer context;
+    gchar *listen_address;
+    GThread *thread;
+    GMutex *startstop_lock;
+    GMutex *started_lock;
+    GMutex *stopped_lock;
+    gboolean has_started;
+    gboolean has_stopped;
+    GCond *started_cond;
+    GCond *stopped_cond;
+    UfoMessenger *msger;
+};
+
+static gpointer run_scheduler (UfoDaemon *daemon);
+
+UfoDaemon *
+ufo_daemon_new (UfoConfig *config, gchar *listen_address)
+{
+    UfoDaemon *daemon;
+
+    g_return_val_if_fail (listen_address != NULL, NULL);
+    g_return_val_if_fail (config != NULL, NULL);
+
+    daemon = UFO_DAEMON (g_object_new (UFO_TYPE_DAEMON, NULL));
+
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    priv->config = config;
+    priv->listen_address = listen_address;
+    priv->manager = ufo_plugin_manager_new (priv->config);
+    priv->scheduler = ufo_scheduler_new (priv->config, NULL);
+#ifdef MPI
+    priv->msger = UFO_MESSENGER (ufo_mpi_messenger_new ());
+#else
+    priv->msger = UFO_MESSENGER (ufo_zmq_messenger_new ());
+#endif
+    return daemon;
+}
+
+static void
+handle_get_num_devices (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    cl_context context;
+
+    UfoMessage *msg = ufo_message_new (UFO_MESSAGE_ACK, sizeof (guint16));
+    cl_uint *num_devices = g_malloc (sizeof (cl_uint));
+    context = ufo_scheduler_get_context (priv->scheduler);
+
+    UFO_RESOURCES_CHECK_CLERR (clGetContextInfo (context,
+                               CL_CONTEXT_NUM_DEVICES,
+                               sizeof (cl_uint),
+                               num_devices,
+                               NULL));
+
+    *(guint16 *) msg->data = (guint16) *num_devices;
+
+    ufo_messenger_send_blocking (priv->msger, msg, 0);
+    ufo_message_free (msg);
+}
+
+static UfoNode *
+remove_dummy_if_present (UfoGraph *graph,
+                         UfoNode *first)
+{
+    UfoNode *real = first;
+
+    if (UFO_IS_DUMMY_TASK (first)) {
+        UfoNode *dummy;
+        GList *successors;
+
+        dummy = first;
+        successors = ufo_graph_get_successors (graph, dummy);
+        g_assert (g_list_length (successors) == 1);
+        real = UFO_NODE (successors->data);
+        g_list_free (successors);
+        ufo_graph_remove_edge (graph, dummy, real);
+    }
+
+    return real;
+}
+
+static gchar *
+read_json (UfoDaemon *daemon, UfoMessage *msg)
+{
+    gchar *json;
+
+    json = g_malloc0 (msg->data_size + 1);
+    memcpy (json, msg->data, msg->data_size);
+
+    return json;
+}
+
+static void
+handle_replicate_json (UfoDaemon *daemon, UfoMessage *msg)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    gchar *json;
+    UfoTaskGraph *graph;
+    GError *error = NULL;
+
+    json = read_json (daemon, msg);
+
+    // send ack
+    UfoMessage *response = ufo_message_new (UFO_MESSAGE_ACK, 0);
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_message_free (response);
+
+    graph = UFO_TASK_GRAPH (ufo_task_graph_new ());
+    ufo_task_graph_read_from_data (graph, priv->manager, json, &error);
+
+    if (error != NULL) {
+        g_printerr ("%s\n", error->message);
+        goto replicate_json_free;
+    }
+
+    ufo_scheduler_run (priv->scheduler, graph, NULL);
+    g_object_unref (priv->scheduler);
+
+    priv->scheduler = ufo_scheduler_new (priv->config, NULL);
+
+replicate_json_free:
+    g_object_unref (graph);
+    g_free (json);
+}
+
+static void
+handle_stream_json (UfoDaemon *daemon, UfoMessage *msg)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    gchar *json;
+    GList *roots;
+    GList *leaves;
+    UfoNode *first;
+    UfoNode *last;
+    GError *error = NULL;
+
+    json = read_json (daemon, msg);
+    // send ack
+    UfoMessage *response = ufo_message_new (UFO_MESSAGE_ACK, 0);
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_message_free (response);
+
+    /* Setup local task graph */
+    priv->task_graph = UFO_TASK_GRAPH (ufo_task_graph_new ());
+    ufo_task_graph_read_from_data (priv->task_graph, priv->manager, json, &error);
+
+    if (error != NULL) {
+        g_printerr ("%s\n", error->message);
+        /* Send error to master */
+        return;
+    }
+
+    roots = ufo_graph_get_roots (UFO_GRAPH (priv->task_graph));
+    g_assert (g_list_length (roots) == 1);
+
+    leaves = ufo_graph_get_leaves (UFO_GRAPH (priv->task_graph));
+    g_assert (g_list_length (leaves) == 1);
+
+    first = UFO_NODE (g_list_nth_data (roots, 0));
+    last = UFO_NODE (g_list_nth_data (leaves, 0));
+
+    first = remove_dummy_if_present (UFO_GRAPH (priv->task_graph), first);
+
+    priv->input_task = ufo_input_task_new ();
+    priv->output_task = ufo_output_task_new (2);
+
+    ufo_graph_connect_nodes (UFO_GRAPH (priv->task_graph),
+                             priv->input_task, first,
+                             GINT_TO_POINTER (0));
+
+    ufo_graph_connect_nodes (UFO_GRAPH (priv->task_graph),
+                             last, priv->output_task,
+                             GINT_TO_POINTER (0));
+
+    priv->scheduler_thread = g_thread_create ((GThreadFunc) run_scheduler, daemon, TRUE, NULL);
+    g_free (json);
+}
+
+static void
+handle_get_structure (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    UfoMessage *response;
+
+    /* TODO move into .h and share between daemon and remote-node */
+    struct _Structure {
+        guint16 n_inputs;
+        guint16 n_dims;
+    } msg_data;
+
+    /* TODO don't hardcode these */
+    msg_data.n_inputs = 1;
+    msg_data.n_dims = 2;
+
+    response = ufo_message_new (UFO_MESSAGE_ACK, sizeof (struct _Structure));
+    *(struct _Structure *) (response->data) = msg_data;
+
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_message_free (response);
+}
+
+static void
+handle_send_inputs (UfoDaemon *daemon, UfoMessage *request)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    UfoRequisition requisition;
+    gpointer context;
+
+    context = ufo_scheduler_get_context (priv->scheduler);
+
+    struct _Header {
+        UfoRequisition requisition;
+        guint64 buffer_size;
+    };
+
+    char *base = request->data;
+    struct _Header *header = (struct _Header *) base;
+
+    /* Receive buffer size */
+    requisition = header->requisition;
+    if (priv->input == NULL) {
+        priv->input = ufo_buffer_new (&requisition, context);
+    }
+    else {
+        if (ufo_buffer_cmp_dimensions (priv->input, &requisition))
+            ufo_buffer_resize (priv->input, &requisition);
+    }
+    memcpy (ufo_buffer_get_host_array (priv->input, NULL),
+            base + sizeof (struct _Header),
+            ufo_buffer_get_size (priv->input));
+    ufo_input_task_release_input_buffer (UFO_INPUT_TASK (priv->input_task), priv->input);
+
+    UfoMessage *response = ufo_message_new (UFO_MESSAGE_ACK, 0);
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_message_free (response);
+}
+
+static void
+handle_get_requisition (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    UfoRequisition requisition;
+
+    /* We need to get the requisition from the last node */
+    ufo_output_task_get_output_requisition (UFO_OUTPUT_TASK (priv->output_task),
+                                            &requisition);
+
+    UfoMessage *msg = ufo_message_new (UFO_MESSAGE_ACK, sizeof (UfoRequisition));
+    memcpy (msg->data, &requisition, msg->data_size);
+    ufo_messenger_send_blocking (priv->msger, msg, NULL);
+    ufo_message_free (msg);
+}
+
+static
+void handle_get_result (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    UfoBuffer *buffer;
+    gsize size;
+
+    buffer = ufo_output_task_get_output_buffer (UFO_OUTPUT_TASK (priv->output_task));
+    size = ufo_buffer_get_size (buffer);
+
+    UfoMessage *response = ufo_message_new (UFO_MESSAGE_ACK, size);
+    memcpy (response->data, ufo_buffer_get_host_array (buffer, NULL), size);
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_output_task_release_output_buffer (UFO_OUTPUT_TASK (priv->output_task), buffer);
+}
+
+static void
+unref_and_free (GObject **object)
+{
+    if (*object) {
+        g_object_unref (*object);
+        *object = NULL;
+    }
+}
+
+static
+void handle_cleanup (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+
+    /*
+     * We send the ACK early on, because we don't want to let the host wait for
+     * actually cleaning up (and waiting some time to unref the input task).
+     */
+    UfoMessage *response = ufo_message_new (UFO_MESSAGE_ACK, 0);
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_message_free (response);
+
+    // TODO check that we don't need to execture this branch wen priv->input is null
+    if (priv->input_task && priv->input) {
+        ufo_input_task_stop (UFO_INPUT_TASK (priv->input_task));
+
+        ufo_input_task_release_input_buffer (UFO_INPUT_TASK (priv->input_task),
+                                             priv->input);
+
+        g_usleep (1.5 * G_USEC_PER_SEC);
+        unref_and_free ((GObject **) &priv->input_task);
+        unref_and_free ((GObject **) &priv->input);
+    }
+
+    unref_and_free ((GObject **) &priv->output_task);
+    unref_and_free ((GObject **) &priv->task_graph);
+
+}
+
+static void
+handle_terminate (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    UfoMessage *response = ufo_message_new (UFO_MESSAGE_ACK, 0);
+    ufo_messenger_send_blocking (priv->msger, response, NULL);
+    ufo_message_free (response);
+
+    if(priv->scheduler_thread != NULL) {
+        g_message ("waiting for scheduler to finish");
+        g_thread_join (priv->scheduler_thread);
+        g_message ("scheduler finished!");
+    }
+
+    ufo_messenger_disconnect (priv->msger);
+}
+
+static gpointer
+run_scheduler (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    g_message ("Start scheduler");
+    ufo_scheduler_run (priv->scheduler, priv->task_graph, NULL);
+
+    g_message ("Done");
+    g_object_unref (priv->scheduler);
+
+    priv->scheduler = ufo_scheduler_new (priv->config, NULL);
+    return NULL;
+}
+
+static void
+ufo_daemon_start_impl (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    g_debug ("UfoDaemon started on address %s", priv->listen_address);
+
+    // tell the calling thread that we have started 
+    g_mutex_lock (priv->started_lock);
+    priv->has_started = TRUE;
+    g_cond_signal (priv->started_cond);
+    g_mutex_unlock (priv->started_lock);
+
+    gboolean wait_for_messages = TRUE;
+    while (wait_for_messages) {
+
+        GError *err = NULL;
+        UfoMessage *msg = ufo_messenger_recv_blocking (priv->msger, &err);
+        if (err != NULL) {
+            /* if daemon is stopped, socket will be closed and msg_recv
+            * will yield an error - we stop
+            */
+            wait_for_messages = FALSE;
+        } else {
+            switch (msg->type) {
+                case UFO_MESSAGE_GET_NUM_DEVICES:
+                    handle_get_num_devices (daemon);
+                    break;
+                case UFO_MESSAGE_STREAM_JSON:
+                    handle_stream_json (daemon, msg);
+                    break;
+                case UFO_MESSAGE_REPLICATE_JSON:
+                    handle_replicate_json (daemon, msg);
+                    break;
+                case UFO_MESSAGE_GET_STRUCTURE:
+                    handle_get_structure (daemon);
+                    break;
+                case UFO_MESSAGE_SEND_INPUTS:
+                    handle_send_inputs (daemon, msg);
+                    break;
+                case UFO_MESSAGE_GET_REQUISITION:
+                    handle_get_requisition (daemon);
+                    break;
+                case UFO_MESSAGE_GET_RESULT:
+                    handle_get_result (daemon);
+                    break;
+                case UFO_MESSAGE_CLEANUP:
+                    handle_cleanup (daemon);
+                    break;
+                case UFO_MESSAGE_TERMINATE:
+                    handle_terminate (daemon);
+                    wait_for_messages = FALSE;
+                    break;
+                default:
+                    g_message ("Unknown message received\n");
+            }
+        }
+        ufo_message_free (msg);
+    }
+
+    // tell calling thread we have stopped
+    g_mutex_lock (priv->stopped_lock);
+    priv->has_stopped = TRUE;
+    g_cond_signal (priv->stopped_cond);
+    g_mutex_unlock (priv->stopped_lock);
+}
+
+void
+ufo_daemon_start (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+
+    g_mutex_lock (priv->startstop_lock);
+    if (priv->has_started) {
+        g_mutex_unlock (priv->startstop_lock);
+        return;
+    }
+
+    /* TODO handle error if unable to connect/bind */
+    ufo_messenger_connect (priv->msger, priv->listen_address, UFO_MESSENGER_SERVER);
+
+    priv->thread = g_thread_create ((GThreadFunc)ufo_daemon_start_impl, daemon, TRUE, NULL);
+    g_return_if_fail (priv->thread != NULL);
+
+    g_mutex_lock (priv->started_lock);
+    while (!priv->has_started)
+        g_cond_wait (priv->started_cond, priv->started_lock);
+    g_mutex_unlock (priv->started_lock);
+
+
+    g_mutex_unlock (priv->startstop_lock);
+}
+
+void
+ufo_daemon_stop (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+    g_mutex_lock (priv->startstop_lock);
+
+    /* HACK we can't call _disconnect() as this has to be run from the
+     * thread running the daemon which might be blocking on recv
+     * - we thus send a TERMINATE message to that thread
+     */
+
+    UfoMessenger *tmp_msger;
+#ifdef MPI
+    tmp_msger = UFO_MESSENGER (ufo_mpi_messenger_new ());
+#else
+    tmp_msger = UFO_MESSENGER (ufo_zmq_messenger_new ());
+#endif
+
+    ufo_messenger_connect (tmp_msger, priv->listen_address, UFO_MESSENGER_CLIENT);
+    UfoMessage *request = ufo_message_new (UFO_MESSAGE_TERMINATE, 0);
+    ufo_messenger_send_blocking (tmp_msger, request, NULL);
+
+    g_thread_join (priv->thread);
+
+    g_mutex_lock (priv->stopped_lock);
+    priv->has_stopped = TRUE;
+    g_cond_signal (priv->stopped_cond);
+    g_mutex_unlock (priv->stopped_lock);
+
+    g_mutex_unlock (priv->startstop_lock);
+}
+
+void ufo_daemon_wait_finish (UfoDaemon *daemon)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (daemon);
+
+    g_mutex_lock (priv->stopped_lock);
+    while (!priv->has_stopped)
+        g_cond_wait (priv->stopped_cond, priv->stopped_lock);
+    g_mutex_unlock (priv->stopped_lock);
+}
+
+static void
+ufo_daemon_dispose (GObject *object)
+{
+    UfoDaemonPrivate *priv;
+    priv = UFO_DAEMON_GET_PRIVATE (object);
+
+    if (priv->task_graph)
+        g_object_unref (priv->task_graph);
+    if (priv->config != NULL)
+        g_object_unref (priv->config);
+    if (priv->msger != NULL)
+        g_object_unref (priv->msger);
+    if (priv->manager != NULL)
+        g_object_unref (priv->manager);
+    if (priv->scheduler != NULL)
+        g_object_unref (priv->scheduler);
+
+    G_OBJECT_CLASS (ufo_daemon_parent_class)->dispose (object);
+}
+
+static void
+ufo_daemon_finalize (GObject *object)
+{
+    UfoDaemonPrivate *priv = UFO_DAEMON_GET_PRIVATE (object);
+    g_mutex_free (priv->startstop_lock);
+    g_cond_free (priv->started_cond);
+
+    G_OBJECT_CLASS (ufo_daemon_parent_class)->finalize (object);
+}
+
+static void
+ufo_daemon_class_init (UfoDaemonClass *klass)
+{
+    GObjectClass *oclass;
+
+    oclass = G_OBJECT_CLASS (klass);
+    oclass->dispose = ufo_daemon_dispose;
+    oclass->finalize = ufo_daemon_finalize;
+
+    g_type_class_add_private (klass, sizeof (UfoDaemonPrivate));
+}
+
+static void
+ufo_daemon_init (UfoDaemon *self)
+{
+    UfoDaemonPrivate *priv;
+    self->priv = priv = UFO_DAEMON_GET_PRIVATE (self);
+    priv->startstop_lock = g_mutex_new ();
+    priv->started_lock = g_mutex_new ();
+    priv->stopped_lock = g_mutex_new ();
+    priv->started_cond = g_cond_new ();
+    priv->stopped_cond = g_cond_new ();
+    priv->has_started = FALSE;
+    priv->has_stopped = FALSE;
+}
diff --git a/ufo/ufo-daemon.h b/ufo/ufo-daemon.h
new file mode 100644
index 0000000..3610310
--- /dev/null
+++ b/ufo/ufo-daemon.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UFO_DAEMON_H
+#define __UFO_DAEMON_H
+
+#if !defined (__UFO_H_INSIDE__) && !defined (UFO_COMPILATION)
+#error "Only <ufo/ufo.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define UFO_TYPE_DAEMON             (ufo_daemon_get_type())
+#define UFO_DAEMON(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UFO_TYPE_DAEMON, UfoDaemon))
+#define UFO_IS_DAEMON(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UFO_TYPE_DAEMON))
+#define UFO_DAEMON_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UFO_TYPE_DAEMON, UfoDaemonClass))
+#define UFO_IS_DAEMON_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_DAEMON))
+#define UFO_DAEMON_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UFO_TYPE_DAEMON, UfoDaemonClass))
+
+typedef struct _UfoDaemon           UfoDaemon;
+typedef struct _UfoDaemonClass      UfoDaemonClass;
+typedef struct _UfoDaemonPrivate    UfoDaemonPrivate;
+
+/**
+ * UfoDaemon:
+ *
+ * TODO: Add documentation
+ */
+struct _UfoDaemon {
+    /*< private >*/
+    GObject parent_instance;
+
+    UfoDaemonPrivate *priv;
+};
+
+/**
+ * UfoDaemonClass:
+ *
+ * #UfoDaemon class
+ */
+struct _UfoDaemonClass {
+    /*< private >*/
+    GObjectClass parent_class;
+};
+
+UfoDaemon *  ufo_daemon_new               (UfoConfig *config, gchar *listen_addr);
+void         ufo_daemon_start             (UfoDaemon *daemon);
+void         ufo_daemon_stop              (UfoDaemon *daemon);
+void         ufo_daemon_wait_finish       (UfoDaemon *daemon);
+GType        ufo_daemon_get_type          (void);
+
+G_END_DECLS
+
+#endif
diff --git a/ufo/ufo-gpu-task-iface.c b/ufo/ufo-gpu-task-iface.c
index bc33ff4..10415b2 100644
--- a/ufo/ufo-gpu-task-iface.c
+++ b/ufo/ufo-gpu-task-iface.c
@@ -27,55 +27,33 @@ gboolean
 ufo_gpu_task_process (UfoGpuTask *task,
                       UfoBuffer **inputs,
                       UfoBuffer *output,
-                      UfoRequisition *requisition,
-                      UfoGpuNode *node)
+                      UfoRequisition *requisition)
 {
-    return UFO_GPU_TASK_GET_IFACE (task)->process (task, inputs, output, requisition, node);
-}
-
-void
-ufo_gpu_task_reduce (UfoGpuTask *task,
-                     UfoBuffer *output,
-                     UfoRequisition *requisition,
-                     UfoGpuNode *node)
-{
-    UFO_GPU_TASK_GET_IFACE (task)->reduce (task, output, requisition, node);
+    return UFO_GPU_TASK_GET_IFACE (task)->process (task, inputs, output, requisition);
 }
 
 gboolean
 ufo_gpu_task_generate (UfoGpuTask *task,
                        UfoBuffer *output,
-                       UfoRequisition *requisition,
-                       UfoGpuNode *node)
+                       UfoRequisition *requisition)
 {
-    return UFO_GPU_TASK_GET_IFACE (task)->generate (task, output, requisition, node);
+    return UFO_GPU_TASK_GET_IFACE (task)->generate (task, output, requisition);
 }
 
 static gboolean
 ufo_gpu_task_process_real (UfoGpuTask *task,
                            UfoBuffer **inputs,
                            UfoBuffer *output,
-                           UfoRequisition *requisition,
-                           UfoGpuNode *node)
+                           UfoRequisition *requisition)
 {
     g_warning ("`process' of UfoGpuTaskInterface not implemented");
     return FALSE;
 }
 
-static void
-ufo_gpu_task_reduce_real (UfoGpuTask *task,
-                          UfoBuffer *output,
-                          UfoRequisition *requisition,
-                          UfoGpuNode *node)
-{
-    g_warning ("`reduce' of UfoGpuTaskInterface not implemented");
-}
-
 static gboolean
 ufo_gpu_task_generate_real (UfoGpuTask *task,
                             UfoBuffer *output,
-                            UfoRequisition *requisition,
-                            UfoGpuNode *node)
+                            UfoRequisition *requisition)
 {
     g_warning ("`generate' of UfoGpuTaskInterface not implemented");
     return FALSE;
@@ -85,6 +63,5 @@ static void
 ufo_gpu_task_default_init (UfoGpuTaskInterface *iface)
 {
     iface->process = ufo_gpu_task_process_real;
-    iface->reduce = ufo_gpu_task_reduce_real;
     iface->generate = ufo_gpu_task_generate_real;
 }
diff --git a/ufo/ufo-gpu-task-iface.h b/ufo/ufo-gpu-task-iface.h
index 1a78f74..4c1ff78 100644
--- a/ufo/ufo-gpu-task-iface.h
+++ b/ufo/ufo-gpu-task-iface.h
@@ -44,34 +44,22 @@ struct _UfoGpuTaskIface {
     /*< private >*/
     UfoTaskIface parent_iface;
 
-    gboolean (*process) (UfoGpuTask     *task,
-                         UfoBuffer     **inputs,
-                         UfoBuffer      *output,
-                         UfoRequisition *requisition,
-                         UfoGpuNode     *node);
-    void     (*reduce)  (UfoGpuTask     *task,
-                         UfoBuffer      *output,
-                         UfoRequisition *requisition,
-                         UfoGpuNode     *node);
-    gboolean (*generate)(UfoGpuTask     *task,
-                         UfoBuffer      *output,
-                         UfoRequisition *requisition,
-                         UfoGpuNode     *node);
+    gboolean (*process)  (UfoGpuTask     *task,
+                          UfoBuffer     **inputs,
+                          UfoBuffer      *output,
+                          UfoRequisition *requisition);
+    gboolean (*generate) (UfoGpuTask     *task,
+                          UfoBuffer      *output,
+                          UfoRequisition *requisition);
 };
 
-gboolean ufo_gpu_task_process (UfoGpuTask       *task,
-                               UfoBuffer       **inputs,
-                               UfoBuffer        *output,
-                               UfoRequisition   *requisition,
-                               UfoGpuNode       *node);
-void     ufo_gpu_task_reduce  (UfoGpuTask       *task,
-                               UfoBuffer        *output,
-                               UfoRequisition   *requisition,
-                               UfoGpuNode       *node);
-gboolean ufo_gpu_task_generate(UfoGpuTask       *task,
-                               UfoBuffer        *output,
-                               UfoRequisition   *requisition,
-                               UfoGpuNode       *node);
+gboolean ufo_gpu_task_process   (UfoGpuTask       *task,
+                                 UfoBuffer       **inputs,
+                                 UfoBuffer        *output,
+                                 UfoRequisition   *requisition);
+gboolean ufo_gpu_task_generate  (UfoGpuTask       *task,
+                                 UfoBuffer        *output,
+                                 UfoRequisition   *requisition);
 
 GType ufo_gpu_task_get_type (void);
 
diff --git a/ufo/ufo-graph.c b/ufo/ufo-graph.c
index 57c2e5b..62ef704 100644
--- a/ufo/ufo-graph.c
+++ b/ufo/ufo-graph.c
@@ -32,9 +32,9 @@ G_DEFINE_TYPE (UfoGraph, ufo_graph, G_TYPE_OBJECT)
 #define UFO_GRAPH_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_GRAPH, UfoGraphPrivate))
 
 struct _UfoGraphPrivate {
-    GList *node_types;
     GList *nodes;
     GList *edges;
+    GList *copies;
 };
 
 enum {
@@ -62,45 +62,6 @@ ufo_graph_new (void)
 }
 
 /**
- * ufo_graph_register_node_type:
- * @graph: A #UfoGraph
- * @type: A #GType
- *
- * Registers @type to be a valid node type of this graph. If a type has not be
- * an added to @graph, any attempt to add such a node will fail.
- */
-void
-ufo_graph_register_node_type (UfoGraph *graph,
-                              GType type)
-{
-    UfoGraphPrivate *priv;
-
-    g_return_if_fail (UFO_IS_GRAPH (graph));
-
-    /* Make sure type is at least a node type */
-    g_return_if_fail (g_type_is_a (type, UFO_TYPE_NODE));
-
-    priv = graph->priv;
-    priv->node_types = g_list_append (priv->node_types, GINT_TO_POINTER (type));
-}
-
-/**
- * ufo_graph_get_registered_node_types:
- * @graph: A #UfoGraph
- *
- * Get all types of nodes that can be added to @graph.
- *
- * Returns: (element-type GType) (transfer container): A list of #GType
- * identifiers that can be added to @graph.
- */
-GList *
-ufo_graph_get_registered_node_types (UfoGraph *graph)
-{
-    g_return_val_if_fail (UFO_IS_GRAPH (graph), NULL);
-    return g_list_copy (graph->priv->node_types);
-}
-
-/**
  * ufo_graph_is_connected:
  * @graph: A #UfoGraph
  * @from: A source node
@@ -124,20 +85,6 @@ ufo_graph_is_connected (UfoGraph *graph,
     return edge != NULL;
 }
 
-static gboolean
-is_valid_node_type (UfoGraphPrivate *priv,
-                    UfoNode *node)
-{
-    for (GList *it = g_list_first (priv->node_types); it != NULL; it = g_list_next (it)) {
-        GType type = (GType) GPOINTER_TO_INT (it->data);
-
-        if (G_TYPE_CHECK_INSTANCE_TYPE (node, type))
-            return TRUE;
-    }
-
-    return FALSE;
-}
-
 static void
 add_node_if_not_found (UfoGraphPrivate *priv,
                        UfoNode *node)
@@ -170,7 +117,10 @@ ufo_graph_connect_nodes (UfoGraph *graph,
     g_return_if_fail (UFO_IS_GRAPH (graph));
     priv = graph->priv;
 
-    g_return_if_fail (is_valid_node_type (priv, source) && is_valid_node_type (priv, target));
+    if (ufo_graph_is_connected (graph, source, target) &&
+        ufo_graph_get_edge_label (graph, source, target) == label) {
+        return;
+    }
 
     edge = g_new0 (UfoEdge, 1);
     edge->source = source;
@@ -219,7 +169,7 @@ ufo_graph_get_num_edges (UfoGraph *graph)
  *
  * Get all edges contained in @graph.
  *
- * Returns: (element-type UfoEdge): a list of #UfoEdge elements or %NULL on
+ * Returns: (transfer full) (element-type UfoEdge): a list of #UfoEdge elements or %NULL on
  * error. Release the list with g_list_free().
  */
 GList *
@@ -395,6 +345,26 @@ ufo_graph_get_leaves (UfoGraph *graph)
     return ufo_graph_get_nodes_filtered (graph, (UfoFilterPredicate) has_no_successor, graph);
 }
 
+static GList *
+get_target_edges (GList *edges,
+                  UfoNode *target)
+{
+    UfoEdge match;
+
+    match.target = target;
+    return g_list_find_all_data (edges, &match, cmp_edge_target);
+}
+
+static GList *
+get_source_edges (GList *edges,
+                  UfoNode *source)
+{
+    UfoEdge match;
+
+    match.source = source;
+    return g_list_find_all_data (edges, &match, cmp_edge_source);
+}
+
 /**
  * ufo_graph_get_predecessors:
  * @graph: A #UfoGraph
@@ -410,15 +380,12 @@ ufo_graph_get_predecessors (UfoGraph *graph,
                             UfoNode *node)
 {
     UfoGraphPrivate *priv;
-    UfoEdge match;
     GList *edges;
     GList *result;
 
     g_return_val_if_fail (UFO_IS_GRAPH (graph), NULL);
     priv = graph->priv;
-
-    match.target = node;
-    edges = g_list_find_all_data (priv->edges, &match, cmp_edge_target);
+    edges = get_target_edges (priv->edges, node);
     result = NULL;
 
     for (GList *it = g_list_first (edges); it != NULL; it = g_list_next (it)) {
@@ -430,6 +397,22 @@ ufo_graph_get_predecessors (UfoGraph *graph,
     return result;
 }
 
+guint
+ufo_graph_get_num_predecessors (UfoGraph *graph,
+                                UfoNode *node)
+{
+    UfoGraphPrivate *priv;
+    GList *edges;
+    guint n_predecessors;
+
+    g_return_val_if_fail (UFO_IS_GRAPH (graph), 0);
+    priv = graph->priv;
+    edges = get_target_edges (priv->edges, node);
+    n_predecessors = g_list_length (edges);
+    g_list_free (edges);
+    return n_predecessors;
+}
+
 /**
  * ufo_graph_get_successors:
  * @graph: A #UfoGraph
@@ -445,15 +428,12 @@ ufo_graph_get_successors (UfoGraph *graph,
                           UfoNode *node)
 {
     UfoGraphPrivate *priv;
-    UfoEdge match;
     GList *edges;
     GList *result;
 
     g_return_val_if_fail (UFO_IS_GRAPH (graph), NULL);
     priv = graph->priv;
-
-    match.source = node;
-    edges = g_list_find_all_data (priv->edges, &match, cmp_edge_source);
+    edges = get_source_edges (priv->edges, node);
     result = NULL;
 
     for (GList *it = g_list_first (edges); it != NULL; it = g_list_next (it)) {
@@ -465,6 +445,164 @@ ufo_graph_get_successors (UfoGraph *graph,
     return result;
 }
 
+guint
+ufo_graph_get_num_successors (UfoGraph *graph,
+                              UfoNode *node)
+{
+    UfoGraphPrivate *priv;
+    GList *edges;
+    guint n_successors;
+
+    g_return_val_if_fail (UFO_IS_GRAPH (graph), 0);
+    priv = graph->priv;
+    edges = get_source_edges (priv->edges, node);
+    n_successors = g_list_length (edges);
+    g_list_free (edges);
+    return n_successors;
+}
+
+static void
+copy_and_connect_successors (UfoGraph *graph,
+                             UfoGraph *copy,
+                             UfoNode *source,
+                             GHashTable *map,
+                             GError **error)
+{
+    GList *successors;
+    UfoNode *copied_source;
+
+    copied_source = g_hash_table_lookup (map, source);
+    successors = ufo_graph_get_successors (graph, source);
+
+    for (GList *jt = g_list_first (successors); jt != NULL; jt = g_list_next (jt)) {
+        UfoNode *target;
+        UfoNode *copied_target;
+        gpointer label;
+
+        target = UFO_NODE (jt->data);
+        copied_target = g_hash_table_lookup (map, target);
+
+        if (copied_target == NULL) {
+            copied_target = ufo_node_copy (target, error);
+
+            if (*error != NULL)
+                return;
+
+            g_hash_table_insert (map, target, copied_target);
+        }
+
+        label = ufo_graph_get_edge_label (graph, source, target);
+        ufo_graph_connect_nodes (copy, copied_source, copied_target, label);
+        copy_and_connect_successors (graph, copy, target, map, error);
+    }
+
+    g_list_free (successors);
+}
+
+/**
+ * ufo_graph_copy:
+ * @graph: A #UfoGraph
+ * @error: Location for an error or %NULL
+ *
+ * Deep-copies the structure of @graph by duplicating all nodes via
+ * ufo_node_copy(). This means the nodes will not be the same but have the same
+ * properties.
+ *
+ * Returns: (transfer full): A copy of @graph or %NULL on error.
+ */
+UfoGraph *
+ufo_graph_copy (UfoGraph *graph,
+                GError **error)
+{
+    UfoGraph *copy;
+    GList *roots;
+    GHashTable *map;    /* maps from real node to copied node */
+    GError *tmp_error = NULL;
+
+    copy = UFO_GRAPH (g_object_new (G_OBJECT_TYPE (graph), NULL));
+    map = g_hash_table_new (NULL, NULL);
+    roots = ufo_graph_get_roots (graph);
+
+    for (GList *it = g_list_first (roots); it != NULL; it = g_list_next (it)) {
+        UfoNode *root;
+        UfoNode *copied_root;
+
+        root = UFO_NODE (it->data);
+        copied_root = ufo_node_copy (root, &tmp_error);
+
+        if (tmp_error != NULL)
+            break;
+
+        g_hash_table_insert (map, root, copied_root);
+        copy_and_connect_successors (graph, copy, root, map, &tmp_error);
+
+        if (tmp_error != NULL)
+            break;
+    }
+
+    g_hash_table_destroy (map);
+    g_list_free (roots);
+
+    if (tmp_error != NULL) {
+        g_warning ("Error: %s", tmp_error->message);
+        g_propagate_error (error, tmp_error);
+        g_object_unref (copy);
+        return NULL;
+    }
+
+    return copy;
+}
+
+static GList *
+append_level (UfoGraph *graph,
+              GList *current_level,
+              GList *result)
+{
+    GList *next_level = NULL;
+
+    result = g_list_append (result, current_level);
+
+    for (GList *it = g_list_first (current_level); it != NULL; it = g_list_next (it)) {
+        GList *successors;
+        UfoNode *node;
+
+        node = UFO_NODE (it->data);
+        successors = ufo_graph_get_successors (graph, node);
+
+        for (GList *jt = g_list_first (successors); jt != NULL; jt = g_list_next (jt)) {
+            UfoNode *succ;
+
+            succ = UFO_NODE (jt->data);
+
+            if (g_list_find (next_level, succ) == NULL)
+                next_level = g_list_append (next_level, succ);
+        }
+    }
+
+    if (next_level == NULL)
+        return result;
+
+    return append_level (graph, next_level, result);
+}
+
+/**
+ * ufo_graph_flatten:
+ * @graph: A #UfoGraph
+ *
+ * Flatten @graph to lists of lists.
+ *
+ * Returns: (transfer full) (element-type GList): a GList of GList, each containing nodes at the same height.
+ */
+GList *
+ufo_graph_flatten (UfoGraph *graph)
+{
+    GList *roots;
+    GList *result = NULL;
+
+    roots = ufo_graph_get_roots (graph);
+    return append_level (graph, roots, result);
+}
+
 /**
  * ufo_graph_expand:
  * @graph: A #UfoGraph
@@ -501,10 +639,24 @@ ufo_graph_expand (UfoGraph *graph,
         gpointer label;
 
         next = UFO_NODE (it->data);
-        copy = ufo_node_copy (next, &error);
-        label = ufo_graph_get_edge_label (graph, orig, next);
-        ufo_graph_connect_nodes (graph, current, copy, label);
-        current = copy;
+
+        /*
+         * Do not copy node if it has more than one input because input data
+         * cannot be reliably associated
+         */
+        if (ufo_graph_get_num_predecessors (graph, next) <= 1) {
+            copy = ufo_node_copy (next, &error);
+            label = ufo_graph_get_edge_label (graph, orig, next);
+            ufo_graph_connect_nodes (graph, current, copy, label);
+            graph->priv->copies = g_list_append (graph->priv->copies, copy);
+            current = copy;
+        }
+        else {
+            label = ufo_graph_get_edge_label (graph, orig, next);
+            ufo_graph_connect_nodes (graph, current, next, label);
+            current = next;
+        }
+
         orig = next;
     }
 
@@ -708,18 +860,18 @@ ufo_graph_dispose (GObject *object)
         priv->nodes = NULL;
     }
 
+    if (priv->copies != NULL) {
+        g_list_foreach (priv->copies, (GFunc) g_object_unref, NULL);
+        g_list_free (priv->copies);
+        priv->copies = NULL;
+    }
+
     G_OBJECT_CLASS (ufo_graph_parent_class)->dispose (object);
 }
 
 static void
 ufo_graph_finalize (GObject *object)
 {
-    UfoGraphPrivate *priv;
-
-    priv = UFO_GRAPH_GET_PRIVATE (object);
-    g_list_free (priv->node_types);
-    priv->node_types = NULL;
-
     G_OBJECT_CLASS (ufo_graph_parent_class)->finalize (object);
 }
 
@@ -739,6 +891,6 @@ ufo_graph_init (UfoGraph *self)
 {
     UfoGraphPrivate *priv;
     self->priv = priv = UFO_GRAPH_GET_PRIVATE (self);
-    priv->node_types = NULL;
     priv->nodes = NULL;
+    priv->copies = NULL;
 }
diff --git a/ufo/ufo-graph.h b/ufo/ufo-graph.h
index 325a6f2..f1ff113 100644
--- a/ufo/ufo-graph.h
+++ b/ufo/ufo-graph.h
@@ -80,10 +80,6 @@ struct _UfoGraphClass {
 };
 
 UfoGraph   *ufo_graph_new                   (void);
-void        ufo_graph_register_node_type    (UfoGraph       *graph,
-                                             GType           type);
-GList      *ufo_graph_get_registered_node_types
-                                            (UfoGraph       *graph);
 void        ufo_graph_connect_nodes         (UfoGraph       *graph,
                                              UfoNode        *source,
                                              UfoNode        *target,
@@ -106,14 +102,21 @@ guint       ufo_graph_get_num_edges         (UfoGraph       *graph);
 GList      *ufo_graph_get_edges             (UfoGraph       *graph);
 GList      *ufo_graph_get_roots             (UfoGraph       *graph);
 GList      *ufo_graph_get_leaves            (UfoGraph       *graph);
+guint       ufo_graph_get_num_predecessors  (UfoGraph       *graph,
+                                             UfoNode        *node);
 GList      *ufo_graph_get_predecessors      (UfoGraph       *graph,
                                              UfoNode        *node);
+guint       ufo_graph_get_num_successors    (UfoGraph       *graph,
+                                             UfoNode        *node);
 GList      *ufo_graph_get_successors        (UfoGraph       *graph,
                                              UfoNode        *node);
 GList      *ufo_graph_get_paths             (UfoGraph       *graph,
                                              UfoFilterPredicate pred);
+GList      *ufo_graph_flatten               (UfoGraph       *graph);
 void        ufo_graph_expand                (UfoGraph       *graph,
                                              GList          *path);
+UfoGraph   *ufo_graph_copy                  (UfoGraph       *graph,
+                                             GError        **error);
 void        ufo_graph_dump_dot              (UfoGraph       *graph,
                                              const gchar    *filename);
 GType       ufo_graph_get_type              (void);
diff --git a/ufo/ufo-group.c b/ufo/ufo-group.c
index 0e5a694..2ce2643 100644
--- a/ufo/ufo-group.c
+++ b/ufo/ufo-group.c
@@ -95,6 +95,13 @@ ufo_group_new (GList *targets,
     return group;
 }
 
+guint
+ufo_group_get_num_targets (UfoGroup *group)
+{
+    g_return_val_if_fail (UFO_IS_GROUP (group), 0);
+    return group->priv->n_targets;
+}
+
 static UfoBuffer *
 pop_or_alloc_buffer (UfoGroupPrivate *priv,
                      guint pos,
@@ -102,7 +109,7 @@ pop_or_alloc_buffer (UfoGroupPrivate *priv,
 {
     UfoBuffer *buffer;
 
-    if (ufo_queue_get_capacity (priv->queues[pos]) < priv->n_targets) {
+    if (ufo_queue_get_capacity (priv->queues[pos]) < (priv->n_targets + 1)) {
         buffer = ufo_buffer_new (requisition, priv->context);
         priv->buffers = g_list_append (priv->buffers, buffer);
         ufo_queue_insert (priv->queues[pos], UFO_QUEUE_PRODUCER, buffer);
@@ -129,10 +136,12 @@ ufo_group_pop_output_buffer (UfoGroup *group,
                              UfoRequisition *requisition)
 {
     UfoGroupPrivate *priv;
-    guint pos;
+    guint pos = 0;
 
     priv = group->priv;
-    pos = priv->pattern == UFO_SEND_SCATTER || UFO_SEND_SEQUENTIAL ? priv->current : 0;
+
+    if ((priv->pattern == UFO_SEND_SCATTER) || (priv->pattern == UFO_SEND_SEQUENTIAL))
+        pos = priv->current;
 
     return pop_or_alloc_buffer (priv, pos, requisition);
 }
@@ -230,6 +239,9 @@ ufo_group_push_input_buffer (UfoGroup *group,
     UfoGroupPrivate *priv;
     gint pos;
 
+    if (group == NULL) {
+        // G_BREAKPOINT();
+    }
     priv = group->priv;
     pos = g_list_index (priv->targets, target);
 
diff --git a/ufo/ufo-group.h b/ufo/ufo-group.h
index e4e7e32..cdb8061 100644
--- a/ufo/ufo-group.h
+++ b/ufo/ufo-group.h
@@ -83,6 +83,7 @@ struct _UfoGroupClass {
 UfoGroup  * ufo_group_new                   (GList          *targets,
                                              gpointer        context,
                                              UfoSendPattern  pattern);
+guint       ufo_group_get_num_targets       (UfoGroup       *group);
 void        ufo_group_set_num_expected      (UfoGroup       *group,
                                              UfoTask        *target,
                                              gint            n_expected);
diff --git a/ufo/ufo-input-task.c b/ufo/ufo-input-task.c
index 68f0cad..3064fa7 100644
--- a/ufo/ufo-input-task.c
+++ b/ufo/ufo-input-task.c
@@ -16,6 +16,7 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
  */
+#include "config.h"
 
 #ifdef __APPLE__
 #include <OpenCL/cl.h>
@@ -25,8 +26,10 @@
 #include <gmodule.h>
 #include <ufo/ufo-input-task.h>
 #include <ufo/ufo-cpu-task-iface.h>
-#include <ufo/ufo-gpu-task-iface.h>
-#include <ufo/ufo-gpu-node.h>
+
+#ifdef HAVE_PYTHON
+#include <Python.h>
+#endif
 
 /**
  * SECTION:ufo-input-task
@@ -63,18 +66,7 @@ enum {
 UfoNode *
 ufo_input_task_new (void)
 {
-    UfoInputTask *task;
-    UfoInputTaskPrivate *priv;
-
-    task = UFO_INPUT_TASK (g_object_new (UFO_TYPE_INPUT_TASK, NULL));
-    priv = task->priv;
-
-    /* TODO: free in_params and queues */
-    priv->in_queue = g_async_queue_new ();
-    priv->out_queue = g_async_queue_new ();
-    priv->active = TRUE;
-
-    return UFO_NODE (task);
+    return UFO_NODE (g_object_new (UFO_TYPE_INPUT_TASK, NULL));
 }
 
 void
@@ -99,13 +91,29 @@ ufo_input_task_release_input_buffer (UfoInputTask *task,
  * Get the input buffer to which we write the data received from the master
  * remote node.
  *
- * Return value: (transfer full): A #UfoBuffer for writing input data.
+ * Return value: (transfer none): A #UfoBuffer for writing input data.
  */
 UfoBuffer *
 ufo_input_task_get_input_buffer (UfoInputTask *task)
 {
+    UfoBuffer *buffer;
     g_return_val_if_fail (UFO_IS_INPUT_TASK (task), NULL);
-    return g_async_queue_pop (task->priv->out_queue);
+
+#ifdef HAVE_PYTHON
+    /*
+     * We have to let the Python interpreter run its threads, because this
+     * function here might block before Python code can insert any buffer.
+     */
+    Py_BEGIN_ALLOW_THREADS
+#endif
+
+    buffer = g_async_queue_pop (task->priv->out_queue);
+
+#ifdef HAVE_PYTHON
+    Py_END_ALLOW_THREADS
+#endif
+
+    return buffer;
 }
 
 static void
@@ -122,7 +130,7 @@ ufo_input_task_get_structure (UfoTask *task,
                               UfoTaskMode *mode)
 {
     *n_inputs = 0;
-    *mode = UFO_TASK_MODE_SINGLE;
+    *mode = UFO_TASK_MODE_GENERATOR;
 }
 
 static void
@@ -134,32 +142,34 @@ ufo_input_task_get_requisition (UfoTask *task,
 
     priv = UFO_INPUT_TASK_GET_PRIVATE (task);
 
-    /* Pop input here but release later in ufo_input_task_process */
-    priv->input = g_async_queue_pop (priv->in_queue);
-    ufo_buffer_get_requisition (priv->input, requisition);
+    /* Pop input here but release later in ufo_input_task_generate */
+    if (priv->active) {
+        priv->input = g_async_queue_pop (priv->in_queue);
+        ufo_buffer_get_requisition (priv->input, requisition);
+    }
 }
 
 static gboolean
-ufo_input_task_process (UfoCpuTask *task,
-                        UfoBuffer **none,
-                        UfoBuffer *output,
-                        UfoRequisition *requisition)
+ufo_input_task_generate (UfoCpuTask *task,
+                         UfoBuffer *output,
+                         UfoRequisition *requisition)
 {
     UfoInputTaskPrivate *priv;
 
     g_return_val_if_fail (UFO_IS_INPUT_TASK (task), FALSE);
     priv = UFO_INPUT_TASK_GET_PRIVATE (task);
 
-    if (!priv->active)
+    if (!priv->active && priv->input == NULL)
         return FALSE;
 
-    ufo_buffer_discard_location (output, UFO_LOCATION_DEVICE);
+    ufo_buffer_discard_location (output);
     ufo_buffer_copy (priv->input, output);
 
     /* input was popped in ufo_input_task_get_requisition */
     g_async_queue_push (priv->out_queue, priv->input);
+    priv->input = NULL;
 
-    return priv->active;
+    return TRUE;
 }
 
 static void
@@ -183,7 +193,7 @@ ufo_task_interface_init (UfoTaskIface *iface)
 static void
 ufo_cpu_task_interface_init (UfoCpuTaskIface *iface)
 {
-    iface->process = ufo_input_task_process;
+    iface->generate = ufo_input_task_generate;
 }
 
 static void
@@ -201,4 +211,8 @@ ufo_input_task_init (UfoInputTask *task)
 {
     task->priv = UFO_INPUT_TASK_GET_PRIVATE (task);
     ufo_task_node_set_plugin_name (UFO_TASK_NODE (task), "input-task");
+
+    task->priv->in_queue = g_async_queue_new ();
+    task->priv->out_queue = g_async_queue_new ();
+    task->priv->active = TRUE;
 }
diff --git a/ufo/ufo-messenger-iface.c b/ufo/ufo-messenger-iface.c
new file mode 100644
index 0000000..d8495cc
--- /dev/null
+++ b/ufo/ufo-messenger-iface.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ #include <ufo/ufo-messenger-iface.h>
+
+typedef UfoMessengerIface UfoMessengerInterface;
+
+void
+ufo_message_free (UfoMessage *msg)
+{
+    if (msg == NULL)
+        return;
+    g_free (msg->data);
+    g_free (msg);
+}
+
+UfoMessage *
+ufo_message_new (UfoMessageType type, guint64 data_size)
+{
+    UfoMessage *msg = g_malloc (sizeof (UfoMessage));
+    msg->type = type;
+    msg->data_size = data_size;
+    if (data_size == 0)
+        msg->data = NULL;
+    else
+        msg->data = g_malloc (data_size);
+    return msg;
+}
+
+G_DEFINE_INTERFACE (UfoMessenger, ufo_messenger, G_TYPE_OBJECT)
+
+/**
+ * UfoTaskError:
+ * @UFO_TASK_ERROR_SETUP: Error during setup of a task.
+ */
+GQuark
+ufo_messenger_error_quark ()
+{
+    return g_quark_from_static_string ("ufo-messenger-error-quark");
+}
+
+/**
+ * ufo_messenger_connect:
+ * @msger: The messenger object
+ * @addr: (transfer none) : The address to connect. This is implementation specific.
+ * @role: The role of the local endpoint (client or server).
+ *
+ * Connects a messenger to and endpoint.
+ */
+void
+ufo_messenger_connect (UfoMessenger *msger,
+                       gchar *addr,
+                       UfoMessengerRole role)
+{
+    UFO_MESSENGER_GET_IFACE (msger)->connect (msger, addr, role);
+}
+
+void
+ufo_messenger_disconnect (UfoMessenger *msger)
+{
+    UFO_MESSENGER_GET_IFACE (msger)->disconnect (msger);
+}
+
+/**
+ * ufo_messenger_send_blocking:
+ * @msger: The messenger object
+ * @request: (transfer none): The request #UfoMessage.
+ * @error: A #GError
+ * Returns: (allow-none) : A #UfoMessage response to the sent request.
+ *
+ * Sends a #UfoMessage request to the connected
+ * endpoint and blocks until the message want fully sent.
+ */
+UfoMessage *
+ufo_messenger_send_blocking (UfoMessenger *msger,
+                             UfoMessage *request,
+                             GError **error)
+{
+    return UFO_MESSENGER_GET_IFACE (msger)->send_blocking (msger, request, error);
+}
+
+/**
+ * ufo_messenger_recv_blocking:
+ * @msger: The messenger object.
+ * @error: The #GError object
+ *
+ * Returns: The received #UfoMessage.
+ *
+ * Receives a #UfoMessage from the connected endpoint and blocks until the
+ * message was fully received.
+ *
+ */
+UfoMessage *
+ufo_messenger_recv_blocking (UfoMessenger *msger,
+                            GError **error)
+{
+    return UFO_MESSENGER_GET_IFACE (msger)->recv_blocking (msger, error);
+}
+
+static void
+ufo_messenger_default_init (UfoMessengerInterface *iface)
+{
+}
diff --git a/ufo/ufo-messenger-iface.h b/ufo/ufo-messenger-iface.h
new file mode 100644
index 0000000..7d9308f
--- /dev/null
+++ b/ufo/ufo-messenger-iface.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UFO_MESSENGER_H
+#define UFO_MESSENGER_H
+
+#if !defined (__UFO_H_INSIDE__) && !defined (UFO_COMPILATION)
+#error "Only <ufo/ufo.h> can be included directly."
+#endif
+
+#include <ufo/ufo-remote-node.h>
+
+G_BEGIN_DECLS
+
+#define UFO_TYPE_MESSENGER             (ufo_messenger_get_type())
+#define UFO_MESSENGER(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UFO_TYPE_MESSENGER, UfoMessenger))
+#define UFO_MESSENGER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UFO_TYPE_MESSENGER, UfoMessengerIface))
+#define UFO_IS_MESSENGER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UFO_TYPE_MESSENGER))
+#define UFO_IS_MESSENGER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_MESSENGER))
+#define UFO_MESSENGER_GET_IFACE(inst)  (G_TYPE_INSTANCE_GET_INTERFACE((inst), UFO_TYPE_MESSENGER, UfoMessengerIface))
+
+#define UFO_MESSENGER_ERROR            ufo_messenger_error_quark()
+
+typedef struct _UfoMessenger         UfoMessenger;
+typedef struct _UfoMessengerIface    UfoMessengerIface;
+typedef struct _UfoMessage           UfoMessage;
+
+
+/**
+ * UfoMessageType:
+ * @UFO_MESSAGE_STREAM_JSON: insert
+ * @UFO_MESSAGE_REPLICATE_JSON: insert
+ * @UFO_MESSAGE_GET_NUM_DEVICES: insert
+ * @UFO_MESSAGE_GET_STRUCTURE: insert
+ * @UFO_MESSAGE_STRUCTURE: insert
+ * @UFO_MESSAGE_GET_REQUISITION: insert
+ * @UFO_MESSAGE_REQUISITION: insert
+ * @UFO_MESSAGE_SEND_INPUTS: insert
+ * @UFO_MESSAGE_GET_RESULT: insert
+ * @UFO_MESSAGE_RESULT: insert
+ * @UFO_MESSAGE_CLEANUP: insert
+ * @UFO_MESSAGE_ACK: insert
+ * @UFO_MESSAGE_TERMINATE: insert
+ *
+ * The type of a message.
+ *
+ */
+typedef enum {
+    UFO_MESSAGE_STREAM_JSON = 0,
+    UFO_MESSAGE_REPLICATE_JSON,
+    UFO_MESSAGE_GET_NUM_DEVICES,
+    UFO_MESSAGE_GET_STRUCTURE,
+    UFO_MESSAGE_STRUCTURE,
+    UFO_MESSAGE_GET_REQUISITION,
+    UFO_MESSAGE_REQUISITION,
+    UFO_MESSAGE_SEND_INPUTS,
+    UFO_MESSAGE_GET_RESULT,
+    UFO_MESSAGE_RESULT,
+    UFO_MESSAGE_CLEANUP,
+    UFO_MESSAGE_TERMINATE,
+    UFO_MESSAGE_ACK
+} UfoMessageType;
+
+/**
+ * UfoMessage:
+ * @type: #UfoMessageType
+ * @data_size: The size of the data field.
+ * @data: A #gpointer to the transferred data
+ *
+ * A message transfered via IPC.
+ *
+ */
+struct _UfoMessage {
+    UfoMessageType type;
+    guint64 data_size;
+    gpointer data;
+};
+
+void ufo_message_free (UfoMessage *msg);
+UfoMessage * ufo_message_new (UfoMessageType type, guint64 data_size);
+
+typedef enum {
+    UFO_MESSENGER_BUFFER_FULL,
+    UFO_MESSENGER_SIZE_MISSMATCH
+} UfoMessengerError;
+
+/**
+ * UfoMessengerRole:
+ * @UFO_MESSENGER_CLIENT: (insert)
+ * @UFO_MESSENGER_SERVER: (insert)
+ *
+ * The role of an connection endpoint.
+ */
+typedef enum {
+    UFO_MESSENGER_CLIENT,
+    UFO_MESSENGER_SERVER
+} UfoMessengerRole;
+
+struct _UfoMessengerIface {
+    /*< private >*/
+    GTypeInterface parent_iface;
+
+    void (*connect)                         (UfoMessenger   *msger,
+                                             gchar *addr,
+                                             UfoMessengerRole role);
+
+    void (*disconnect)                      (UfoMessenger   *msger);
+
+    UfoMessage * (*send_blocking)           (UfoMessenger *msger,
+                                             UfoMessage *request,
+                                             GError **error);
+
+    UfoMessage * (*recv_blocking)           (UfoMessenger *msger,
+                                             GError **error);
+};
+
+
+void        ufo_messenger_connect           (UfoMessenger *msger,
+                                             gchar *addr,
+                                             UfoMessengerRole role);
+
+void        ufo_messenger_disconnect        (UfoMessenger     *msger);
+
+UfoMessage *ufo_messenger_send_blocking     (UfoMessenger     *msger,
+                                             UfoMessage *request,
+                                             GError          **error);
+
+UfoMessage *ufo_messenger_recv_blocking     (UfoMessenger     *msger,
+                                             GError          **error);
+
+GQuark      ufo_messenger_error_quark       (void);
+GType       ufo_messenger_get_type          (void);
+
+G_END_DECLS
+
+#endif
diff --git a/ufo/ufo-mpi-messenger.c b/ufo/ufo-mpi-messenger.c
new file mode 100644
index 0000000..2172dbb
--- /dev/null
+++ b/ufo/ufo-mpi-messenger.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ufo/ufo-mpi-messenger.h>
+#include <mpi.h>
+#include <string.h>
+#include <unistd.h>
+
+static void ufo_messenger_interface_init (UfoMessengerIface *iface);
+
+#define UFO_MPI_MESSENGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_MPI_MESSENGER, UfoMpiMessengerPrivate))
+
+G_DEFINE_TYPE_WITH_CODE (UfoMpiMessenger, ufo_mpi_messenger, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (UFO_TYPE_MESSENGER,
+                                                ufo_messenger_interface_init))
+
+
+struct _UfoMpiMessengerPrivate {
+    gint own_rank;
+    gint remote_rank;
+    gint pid;
+    gint global_size;
+    gboolean connected;
+    GMutex *mutex;
+    gboolean free_mutex;
+    UfoMessengerRole role;
+};
+
+/* C99 allows flexible length structs that we use to map
+* arbitrary frame lengths that are transferred via mpi.
+* Note: Sizes of datatypes should be fixed and equal on all plattforms
+* (i.e. don't use a gsize as it has different size on x86 & x86_64)
+*/
+typedef struct _DataFrame {
+    UfoMessageType type;
+    guint64 data_size;
+    // variable length data field
+    char data[];
+} DataFrame;
+
+/*
+ * In most MPI implementations, calls to MPI_Send/Recv are not thread safe.
+ * If a global_mutex != NULL is specified, we use this global lock to serialize
+ * the Send/Recv. If global_mutex is NULL, we don't synchronize (i.e. when
+ * calling from ufo-daemon since it only holds a single messaging thread).
+ * If MPI_THREADS_MULTIPLE is supported, we wouldn't need a global lock - however,
+ * on most InfiniBand systems, this is not the case.
+ */
+UfoMpiMessenger *
+ufo_mpi_messenger_new ()
+{
+    UfoMpiMessenger *msger;
+    msger = UFO_MPI_MESSENGER (g_object_new (UFO_TYPE_MPI_MESSENGER, NULL));
+
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (msger);
+    MPI_Comm_rank (MPI_COMM_WORLD, &priv->own_rank);
+    MPI_Comm_size (MPI_COMM_WORLD, &priv->global_size);
+
+    gint provided_thread_level;
+    MPI_Query_thread (&provided_thread_level);
+    if (provided_thread_level >= MPI_THREAD_MULTIPLE) {
+        /* don't use global mutex, we rely on the MPI implementation to be thread
+         * safe. TODO: don't even use a per-thread lock in this case.
+         */
+        priv->mutex = g_mutex_new ();
+        priv->free_mutex = TRUE;
+    } else if (provided_thread_level == MPI_THREAD_SERIALIZED) {
+        g_message ("The MPI implementation does not support MPI_THREAD_MULTIPLE");
+        g_message ("Using global lock for MPI communication, performance may be degraded.");
+        static GStaticMutex static_mutex = G_STATIC_MUTEX_INIT;
+        priv->mutex = g_static_mutex_get_mutex (&static_mutex);
+    } else {
+        g_critical ("Required thread level MPI_THREAD_SERIALIZED not available in used MPI implementation");
+    }
+
+    return msger;
+}
+
+void
+ufo_mpi_messenger_connect (UfoMessenger *msger, gchar *addr, UfoMessengerRole role)
+{
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (msger);
+    g_mutex_lock (priv->mutex);
+
+    if (role == UFO_MESSENGER_CLIENT) {
+        int remote_rank = g_ascii_strtoll (addr, NULL, 0);
+        priv->remote_rank = remote_rank;
+        g_debug ("[%d:%d]: CLIENT connected to: %d", priv->pid, priv->own_rank, priv->remote_rank);
+    } else {
+        priv->remote_rank = 0;
+        g_debug ("[%d:%d]: SERVER connected to: %d", priv->pid, priv->own_rank, priv->remote_rank);
+    }
+    priv->connected = TRUE;
+    g_mutex_unlock (priv->mutex);
+}
+
+void
+ufo_mpi_messenger_disconnect (UfoMessenger *msger)
+{
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (msger);
+    g_mutex_lock (priv->mutex);
+    priv->connected = FALSE; 
+    g_mutex_unlock (priv->mutex);
+}
+
+UfoMessage *
+ufo_mpi_messenger_send_blocking (UfoMessenger *msger,
+                                 UfoMessage *request_msg,
+                                 GError **error)
+{
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (msger);
+
+    g_mutex_lock (priv->mutex);
+    g_assert (priv->connected == TRUE);
+
+    // we send in two phaess: first send the data frame of fixed size
+    // then the receiver knows how much bytes will follow in the second send
+    DataFrame *request_frame = g_malloc0 (sizeof (DataFrame));
+    request_frame->type = request_msg->type;
+    request_frame->data_size = request_msg->data_size;
+
+    // send preflight
+    // g_debug ("[%d:%d] SEND sending preflight to: %d", priv->pid, priv->own_rank, priv->remote_rank);
+    MPI_Ssend (request_frame, sizeof (DataFrame), MPI_CHAR, priv->remote_rank, 0, MPI_COMM_WORLD);
+    // g_debug ("[%d:%d] SEND preflight done to: %d", priv->pid, priv->own_rank, priv->remote_rank);
+
+    // send payload
+    if (request_msg->data_size > 0) {
+        g_debug ("[%d:%d] SEND sending payload to: %d, size: %lu", priv->pid, priv->own_rank, priv->remote_rank, request_msg->data_size);
+        int err = MPI_Ssend (request_msg->data, request_msg->data_size, MPI_CHAR, priv->remote_rank, 0, MPI_COMM_WORLD);
+        if (err != MPI_SUCCESS) {
+            g_critical ("error on MPI_Ssend: %d", err);
+        } 
+        g_debug ("[%d:%d] SEND payload done to: %d", priv->pid, priv->own_rank, priv->remote_rank);
+    }
+
+    if (request_msg->type == UFO_MESSAGE_ACK) {
+        goto finalize;
+    }
+    
+    // receive the response
+    MPI_Status status;
+    UfoMessage *response = g_malloc0 (sizeof (UfoMessage));
+
+    // reuse the memory buffer
+    DataFrame *response_frame = request_frame;
+    // g_debug ("[%d:%d] SEND waiting for response preflight from: %d", priv->pid, priv->own_rank, priv->remote_rank);
+    // if (priv->own_rank == 2)
+    // G_BREAKPOINT();
+    MPI_Recv (response_frame, sizeof (DataFrame), MPI_CHAR, priv->remote_rank, 0, MPI_COMM_WORLD, &status);
+    // g_debug ("[%d:%d] SEND response preflight received from: %d SIZE:%lu", priv->pid, priv->own_rank, priv->remote_rank, response_frame->data_size);
+ 
+    response->type = response_frame->type;
+    response->data_size = response_frame->data_size;
+
+    if (response_frame->data_size > 0) {
+        gpointer buff = g_malloc0 (response_frame->data_size);
+        g_debug ("[%d:%d] SEND waiting for response payload from: %d", priv->pid, priv->own_rank, priv->remote_rank);
+        MPI_Recv (buff, response_frame->data_size, MPI_CHAR, priv->remote_rank, 0, MPI_COMM_WORLD, &status);
+        g_debug ("[%d:%d] SEND payload received from: %d", priv->pid, priv->own_rank, priv->remote_rank);
+        response->data = buff;
+    }
+
+    goto finalize;
+
+    finalize:
+        g_mutex_unlock (priv->mutex);
+        return response;
+}
+
+UfoMessage *
+ufo_mpi_messenger_recv_blocking (UfoMessenger *msger,
+                                 GError **error)
+{
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (msger);
+
+    g_mutex_lock (priv->mutex);
+    g_assert (priv->connected == TRUE);
+
+    UfoMessage *response = g_malloc0 (sizeof (DataFrame));
+    DataFrame *frame = g_malloc0 (sizeof (DataFrame));
+    MPI_Status status;
+    
+    g_debug ("[%d:%d] RECV waiting for preflight from %d", priv->pid, priv->own_rank, priv->remote_rank);
+    int ret = MPI_Recv (frame, sizeof (DataFrame), MPI_CHAR, priv->remote_rank, 0, MPI_COMM_WORLD, &status);
+
+    if (ret != MPI_SUCCESS)
+        g_critical ("error on recv: %d", ret);
+
+    g_debug ("[%d:%d] RECV preflight received from %d, size: %lu", priv->pid, priv->own_rank, priv->remote_rank, frame->data_size);
+    response->type = frame->type;
+    response->data_size = frame->data_size;
+
+    if (frame->data_size > 0) {
+        gpointer buff = g_malloc0 (frame->data_size);
+        g_debug ("[%d:%d] RECV waiting for payload with size %lu from %d", priv->pid, priv->own_rank, frame->data_size, priv->remote_rank);
+        MPI_Recv (buff, frame->data_size, MPI_CHAR, priv->remote_rank, 0, MPI_COMM_WORLD, &status);
+        g_debug ("[%d:%d] RECV payload received from %d, size: %lu", priv->pid, priv->own_rank, priv->remote_rank, frame->data_size);
+        response->data = buff;
+    }
+    g_mutex_unlock (priv->mutex);
+    return response;
+}
+
+static void
+ufo_messenger_interface_init (UfoMessengerIface *iface)
+{
+    iface->connect = ufo_mpi_messenger_connect;
+    iface->disconnect = ufo_mpi_messenger_disconnect;
+    iface->send_blocking = ufo_mpi_messenger_send_blocking;
+    iface->recv_blocking = ufo_mpi_messenger_recv_blocking;
+}
+
+
+static void
+ufo_mpi_messenger_dispose (GObject *object)
+{
+    ufo_mpi_messenger_disconnect (UFO_MESSENGER (object));
+}
+
+static void
+ufo_mpi_messenger_finalize (GObject *object)
+{
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (object);
+
+    if (priv->free_mutex)
+        g_mutex_free (priv->mutex);
+}
+
+static void
+ufo_mpi_messenger_class_init (UfoMpiMessengerClass *klass)
+{
+    GObjectClass *oclass = G_OBJECT_CLASS (klass);
+    oclass->dispose      = ufo_mpi_messenger_dispose;
+    oclass->finalize     = ufo_mpi_messenger_finalize;
+
+    g_type_class_add_private (klass, sizeof(UfoMpiMessengerPrivate));
+}
+
+static void
+ufo_mpi_messenger_init (UfoMpiMessenger *msger)
+{
+    UfoMpiMessengerPrivate *priv = UFO_MPI_MESSENGER_GET_PRIVATE (msger);
+    priv->connected = FALSE;
+}
diff --git a/ufo/ufo-mpi-messenger.h b/ufo/ufo-mpi-messenger.h
new file mode 100644
index 0000000..34235ce
--- /dev/null
+++ b/ufo/ufo-mpi-messenger.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UFO_MPI_MESSENGER_H
+#define __UFO_MPI_MESSENGER_H
+
+#include <ufo/ufo-remote-node.h>
+#include <ufo/ufo-messenger-iface.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define UFO_TYPE_MPI_MESSENGER             (ufo_mpi_messenger_get_type())
+#define UFO_MPI_MESSENGER(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UFO_TYPE_MPI_MESSENGER, UfoMpiMessenger))
+#define UFO_IS_MPI_MESSENGER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UFO_TYPE_MPI_MESSENGER))
+#define UFO_MPI_MESSENGER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UFO_TYPE_MPI_MESSENGER, UfoMpiMessengerClass))
+#define UFO_IS_MPI_MESSENGER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_MPI_MESSENGER))
+#define UFO_MPI_MESSENGER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UFO_TYPE_MPI_MESSENGER, UfoMpiMessengerClass))
+
+typedef struct _UfoMpiMessenger           UfoMpiMessenger;
+typedef struct _UfoMpiMessengerClass      UfoMpiMessengerClass;
+typedef struct _UfoMpiMessengerPrivate    UfoMpiMessengerPrivate;
+
+struct _UfoMpiMessenger {
+    /*< private >*/
+    GObject parent_instance;
+
+    UfoMpiMessengerPrivate *priv;
+};
+
+struct _UfoMpiMessengerClass {
+    /*< private >*/
+    GObjectClass parent_class;
+};
+
+UfoMpiMessenger    *ufo_mpi_messenger_new           (void);
+GType               ufo_mpi_messenger_get_type      (void);
+
+void                ufo_mpi_messenger_connect       (UfoMessenger *msger,
+                                                     gchar *addr,
+                                                     UfoMessengerRole role);
+
+void                ufo_mpi_messenger_disconnect    (UfoMessenger *msg);
+
+UfoMessage         *ufo_mpi_messenger_send_blocking (UfoMessenger *msger,
+                                                     UfoMessage *request,
+                                                     GError **error);
+
+UfoMessage         *ufo_mpi_messenger_recv_blocking (UfoMessenger *msger,
+                                                     GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/ufo/ufo-node.c b/ufo/ufo-node.c
index a60e690..2e9d941 100644
--- a/ufo/ufo-node.c
+++ b/ufo/ufo-node.c
@@ -31,6 +31,8 @@ G_DEFINE_TYPE (UfoNode, ufo_node, G_TYPE_OBJECT)
 
 struct _UfoNodePrivate {
     UfoNode  *copied_from;
+    guint     index;
+    guint     total;
     gpointer  label;
 };
 
@@ -64,26 +66,34 @@ ufo_node_get_label (UfoNode *node)
     return node->priv->label;
 }
 
-static UfoNode *
-ufo_node_copy_real (UfoNode *node,
-                    GError **error)
+static void
+copy_properties (GObject *dst,
+                 GObject *src)
 {
-    GObject *copy;
     GParamSpec **props;
     guint n_props;
 
-    copy = g_object_new (G_OBJECT_TYPE (node), NULL);
-    props = g_object_class_list_properties (G_OBJECT_GET_CLASS (node), &n_props);
+    props = g_object_class_list_properties (G_OBJECT_GET_CLASS (src), &n_props);
 
     for (guint i = 0; i < n_props; i++) {
         GValue value = {0};
 
         g_value_init (&value, props[i]->value_type);
-        g_object_get_property (G_OBJECT (node), props[i]->name, &value);
-        g_object_set_property (G_OBJECT (copy), props[i]->name, &value);
+        g_object_get_property (G_OBJECT (src), props[i]->name, &value);
+        g_object_set_property (G_OBJECT (dst), props[i]->name, &value);
     }
 
     g_free (props);
+}
+
+static UfoNode *
+ufo_node_copy_real (UfoNode *node,
+                    GError **error)
+{
+    GObject *copy;
+
+    copy = g_object_new (G_OBJECT_TYPE (node), NULL);
+    copy_properties (copy, G_OBJECT (node));
     return UFO_NODE (copy);
 }
 
@@ -99,13 +109,25 @@ ufo_node_equal_real (UfoNode *n1,
            n2->priv->copied_from == n1;
 }
 
+static void
+update_total (UfoNode *node,
+              guint total)
+{
+    node->priv = UFO_NODE_GET_PRIVATE (node);
+    node->priv->total = total;
+
+    if (node->priv->copied_from != NULL)
+        update_total (node->priv->copied_from, total);
+}
+
 /**
  * ufo_node_copy:
  * @node: A #UfoNode
  * @error: Location for an error
  *
  * Get a copy of @node. How "deep" the copy is, depends on the inherited
- * implementation of @node.
+ * implementation of @node. The copy receives an new index and the total amount
+ * of nodes is increased by one.
  *
  * Returns: (transfer full): Copy of @node.
  */
@@ -116,10 +138,49 @@ ufo_node_copy (UfoNode *node,
     UfoNode *offspring;
 
     offspring = UFO_NODE_GET_CLASS (node)->copy (node, error);
+    offspring->priv->label = node->priv->label;
     offspring->priv->copied_from = node;
+    offspring->priv->index = node->priv->total;
+    offspring->priv->total = offspring->priv->index + 1;
+
+    update_total (node, offspring->priv->total);
+
     return offspring;
 }
 
+/**
+ * ufo_node_get_index:
+ * @node: A #UfoNode
+ *
+ * Get the index of this node. When a graph is expanded, nodes are copied. The
+ * original node has index 1, all successive copies receive a monotonous
+ * increasing index. The total amount of copied nodes can be queried with
+ * ufo_node_get_total().
+ *
+ * Returns: The index of @node.
+ */
+guint
+ufo_node_get_index (UfoNode *node)
+{
+    g_return_val_if_fail (UFO_IS_NODE (node), 0);
+    return node->priv->index;
+}
+
+/**
+ * ufo_node_get_total:
+ * @node: A #UfoNode
+ *
+ * Get the total amount of copied nodes.
+ *
+ * Returns: The number of copied nodes.
+ */
+guint
+ufo_node_get_total (UfoNode *node)
+{
+    g_return_val_if_fail (UFO_IS_NODE (node), 0);
+    return node->priv->total;
+}
+
 gboolean
 ufo_node_equal (UfoNode *n1,
                 UfoNode *n2)
@@ -143,5 +204,7 @@ ufo_node_init (UfoNode *self)
     self->priv = priv = UFO_NODE_GET_PRIVATE (self);
 
     priv->copied_from = NULL;
+    priv->index = 0;
+    priv->total = 1;
     priv->label = NULL;
 }
diff --git a/ufo/ufo-node.h b/ufo/ufo-node.h
index 9d5eb18..f13b394 100644
--- a/ufo/ufo-node.h
+++ b/ufo/ufo-node.h
@@ -73,6 +73,8 @@ UfoNode     *ufo_node_copy      (UfoNode    *node,
                                  GError    **error);
 gboolean     ufo_node_equal     (UfoNode    *n1,
                                  UfoNode    *n2);
+guint        ufo_node_get_index (UfoNode    *node);
+guint        ufo_node_get_total (UfoNode    *node);
 GType        ufo_node_get_type  (void);
 
 G_END_DECLS
diff --git a/ufo/ufo-output-task.c b/ufo/ufo-output-task.c
index a14f6c4..f2c7dc4 100644
--- a/ufo/ufo-output-task.c
+++ b/ufo/ufo-output-task.c
@@ -127,7 +127,7 @@ ufo_output_task_get_structure (UfoTask *task,
 {
     UfoOutputTaskPrivate *priv = UFO_OUTPUT_TASK_GET_PRIVATE (task);
 
-    *mode = UFO_TASK_MODE_SINGLE;
+    *mode = UFO_TASK_MODE_PROCESSOR;
     *n_inputs = 1;
     *in_params = g_new0 (UfoInputParam, 1);
     (*in_params)[0].n_dims = priv->n_dims;
diff --git a/ufo/ufo-plugin-manager.c b/ufo/ufo-plugin-manager.c
index d45ea6c..00a3936 100644
--- a/ufo/ufo-plugin-manager.c
+++ b/ufo/ufo-plugin-manager.c
@@ -32,9 +32,13 @@
  *
  * The plugin manager opens shared object modules searched for in locations
  * specified with ufo_plugin_manager_add_paths(). An #UfoFilter can be
- * instantiated with ufo_plugin_manager_get_filter() with a one-to-one mapping
- * between filter name xyz and module name libfilterxyz.so. Any errors are
+ * instantiated with ufo_plugin_manager_get_task() with a one-to-one mapping
+ * between filter name xyz and module name libufofilterxyz.so. Any errors are
  * reported as one of #UfoPluginManagerError codes.
+ *
+ * Apart from standard locations and paths passed through the #UfoConfig object,
+ * #UfoPluginManager also looks into the path that is specified in the
+ * UFO_PLUGIN_PATH environment variable.
  */
 
 G_DEFINE_TYPE_WITH_CODE (UfoPluginManager, ufo_plugin_manager, G_TYPE_OBJECT,
@@ -123,59 +127,37 @@ ufo_plugin_manager_new (UfoConfig *config)
     return manager;
 }
 
-static gchar *
-transform_string (const gchar *pattern,
-                  const gchar *s,
-                  const gchar *separator)
-{
-    gchar **sv;
-    gchar *transformed;
-    gchar *result;
-
-    sv = g_strsplit_set (s, "-_ ", -1);
-    transformed = g_strjoinv (separator, sv);
-    result = g_strdup_printf (pattern, transformed);
-
-    g_strfreev (sv);
-    g_free (transformed);
-    return result;
-}
 
 /**
- * ufo_plugin_manager_get_task:
+ * ufo_plugin_manager_get_plugin:
  * @manager: A #UfoPluginManager
- * @name: Name of the plugin.
+ * @func_name: Name of the constructor function.
+ * @module_name: Filename of the shared object.
  * @error: return location for a GError or %NULL
  *
- * Load a #UfoFilter module and return an instance. The shared object name must
- * be * constructed as "libfilter at name.so".
- *
- * Since: 0.2, the error parameter is available
+ * Load a module and return an instance.
  *
- * Returns: (transfer none): (allow-none): #UfoFilter or %NULL if module cannot be found
+ * Returns: (transfer full): (allow-none): #gpointer or %NULL if module cannot be found
  */
-UfoNode *
-ufo_plugin_manager_get_task (UfoPluginManager *manager, const gchar *name, GError **error)
+gpointer
+ufo_plugin_manager_get_plugin (UfoPluginManager *manager, 
+                              const gchar *func_name,
+                              const gchar *module_name,
+                              GError **error)
 {
-    g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) && (name != NULL), NULL);
-    UfoPluginManagerPrivate *priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager);
-    UfoNode *node;
-    NewFunc *func;
-    GModule *module;
-    gchar *func_name = NULL;
-    gchar *module_name = NULL;
-
-    g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) && name != NULL, NULL);
+    g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) &&
+                          func_name != NULL &&
+                          module_name != NULL, NULL);
 
-    if (!g_strcmp0 (name, "[dummy]"))
-        return ufo_dummy_task_new ();
+    UfoPluginManagerPrivate *priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager);
+    gpointer plugin = NULL;
+    NewFunc *func = NULL;
+    GModule *module = NULL;
 
-    func = g_hash_table_lookup (priv->new_funcs, name);
+    gchar *key = g_strjoin("@", func_name, module_name, NULL);
+    func = g_hash_table_lookup (priv->new_funcs, key);
 
     if (func == NULL) {
-        module_name = transform_string ("libufofilter%s.so", name, NULL);
-        func_name = transform_string ("ufo_%s_task_new", name, "_");
-
         gchar *path = plugin_manager_get_path (priv, module_name);
 
         if (path == NULL) {
@@ -207,54 +189,51 @@ ufo_plugin_manager_get_task (UfoPluginManager *manager, const gchar *name, GErro
         }
 
         priv->modules = g_slist_append (priv->modules, module);
-        g_hash_table_insert (priv->new_funcs, g_strdup (name), func);
-
-        g_free (func_name);
-        g_free (module_name);
+        g_hash_table_insert (priv->new_funcs, g_strdup (key), func);
+        g_free (key);
     }
 
-    node = (*func) ();
-    ufo_task_node_set_plugin_name (UFO_TASK_NODE (node), name);
-    g_message ("UfoPluginManager: Created %s-%p", name, (gpointer) node);
-
-    return node;
+    plugin = (*func) ();
+    return plugin;
 
 handle_error:
-    g_free (module_name);
-    g_free (func_name);
+    g_free (key);
     return NULL;
 }
 
 /**
- * ufo_plugin_manager_get_all_task_names:
+ * ufo_plugin_get_all_plugin_names:
  * @manager: A #UfoPluginManager
+ * @filename_regex: Regex for filenames
+ * @filename_pattern: Pattern according with the files will be searched
  *
- * Return a list with potential filter names that match shared objects in all
+ * Return a list with potential plugin names that match shared objects in all
  * search paths.
  *
  * Return value: (element-type utf8) (transfer full): List of strings with filter names
  */
-GList *
-ufo_plugin_manager_get_all_task_names (UfoPluginManager *manager)
+GList*
+ufo_plugin_get_all_plugin_names (UfoPluginManager *manager,
+                                 const GRegex *filename_regex,
+                                 const gchar *filename_pattern)
 {
     g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager), NULL);
     UfoPluginManagerPrivate *priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager);
     GList *result = NULL;
 
-    GRegex *regex = g_regex_new ("libufofilter([A-Za-z]+).so", 0, 0, NULL);
     GMatchInfo *match_info = NULL;
 
     for (GList *path = g_list_first (priv->search_paths); path != NULL; path = g_list_next (path)) {
         glob_t glob_vector;
         gchar *pattern;
 
-        pattern = g_build_filename ((gchar *) path->data, "libufofilter*.so", NULL);
+        pattern = g_build_filename ((gchar *) path->data, filename_pattern, NULL);
         glob (pattern, GLOB_MARK | GLOB_TILDE, NULL, &glob_vector);
         g_free (pattern);
         gsize i = 0;
 
         while (i < glob_vector.gl_pathc) {
-            g_regex_match (regex, glob_vector.gl_pathv[i], 0, &match_info);
+            g_regex_match (filename_regex, glob_vector.gl_pathv[i], 0, &match_info);
 
             if (g_match_info_matches (match_info)) {
                 gchar *word = g_match_info_fetch (match_info, 1);
@@ -266,7 +245,6 @@ ufo_plugin_manager_get_all_task_names (UfoPluginManager *manager)
     }
 
     g_match_info_free (match_info);
-    g_regex_unref (regex);
     return result;
 }
 
@@ -330,8 +308,15 @@ ufo_plugin_manager_finalize (GObject *gobject)
     UfoPluginManager *manager = UFO_PLUGIN_MANAGER (gobject);
     UfoPluginManagerPrivate *priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager);
 
-    g_slist_foreach (priv->modules, (GFunc) g_module_close, NULL);
-    g_slist_free (priv->modules);
+    /* XXX: This is a necessary hack! We return a full reference for
+     * ufo_plugin_manager_get_task() so that the Python run-time can cleanup
+     * the tasks that are assigned. However, there is no relationship between
+     * graphs, tasks and the plugin manager and it might happen, that the plugin
+     * manager is destroy before the graph which in turn would unref invalid
+     * objects. So, we just don't close the modules and hope for the best.
+     */
+    /* g_slist_foreach (priv->modules, (GFunc) g_module_close, NULL); */
+    /* g_slist_free (priv->modules); */
 
     g_list_foreach (priv->search_paths, (GFunc) g_free, NULL);
     g_list_free (priv->search_paths);
@@ -359,6 +344,7 @@ ufo_plugin_manager_class_init (UfoPluginManagerClass *klass)
 static void
 ufo_plugin_manager_init (UfoPluginManager *manager)
 {
+    static const gchar *PATH_VAR = "UFO_PLUGIN_PATH";
     UfoPluginManagerPrivate *priv;
 
     manager->priv = priv = UFO_PLUGIN_MANAGER_GET_PRIVATE (manager);
@@ -366,4 +352,90 @@ ufo_plugin_manager_init (UfoPluginManager *manager)
     priv->search_paths = NULL;
     priv->new_funcs = g_hash_table_new_full (g_str_hash, g_str_equal,
                                              g_free, g_free);
+
+    if (g_getenv (PATH_VAR)) {
+        priv->search_paths = g_list_append (priv->search_paths,
+                                            g_strdup (g_getenv (PATH_VAR)));
+    }
+}
+
+
+
+static gchar *
+transform_string (const gchar *pattern,
+                  const gchar *s,
+                  const gchar *separator)
+{
+    gchar **sv;
+    gchar *transformed;
+    gchar *result;
+
+    sv = g_strsplit_set (s, "-_ ", -1);
+    transformed = g_strjoinv (separator, sv);
+    result = g_strdup_printf (pattern, transformed);
+
+    g_strfreev (sv);
+    g_free (transformed);
+    return result;
+}
+
+/**
+ * ufo_plugin_manager_get_task:
+ * @manager: A #UfoPluginManager
+ * @name: Name of the plugin.
+ * @error: return location for a GError or %NULL
+ *
+ * Load a #UfoFilter module and return an instance. The shared object name must
+ * be * constructed as "libfilter at name.so".
+ *
+ * Since: 0.2, the error parameter is available
+ *
+ * Returns: (transfer full): (allow-none): #UfoFilter or %NULL if module cannot be found
+ */
+UfoTaskNode *
+ufo_plugin_manager_get_task (UfoPluginManager *manager, const gchar *name, GError **error)
+{
+    g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager) && name != NULL, NULL);
+    UfoTaskNode *node;
+
+    if (!g_strcmp0 (name, "[dummy]"))
+        return UFO_TASK_NODE (ufo_dummy_task_new ());
+
+    gchar *module_name = transform_string ("libufofilter%s.so", name, NULL);
+    gchar *func_name = transform_string ("ufo_%s_task_new", name, "_");
+    node = UFO_TASK_NODE (ufo_plugin_manager_get_plugin (manager,
+                                          func_name,
+                                          module_name,
+                                          error));
+
+    ufo_task_node_set_plugin_name (node, name);
+
+    g_free (func_name);
+    g_free (module_name);
+
+    g_debug ("UfoPluginManager: Created %s-%p", name, (gpointer) node);
+    return node;
+}
+
+/**
+ * ufo_plugin_manager_get_all_task_names:
+ * @manager: A #UfoPluginManager
+ *
+ * Return a list with potential filter names that match shared objects in all
+ * search paths.
+ *
+ * Return value: (element-type utf8) (transfer full): List of strings with filter names
+ */
+GList *
+ufo_plugin_manager_get_all_task_names (UfoPluginManager *manager)
+{
+    g_return_val_if_fail (UFO_IS_PLUGIN_MANAGER (manager), NULL);
+    GRegex *regex = g_regex_new ("libufofilter([A-Za-z]+).so", 0, 0, NULL);
+
+    GList *result = ufo_plugin_get_all_plugin_names(manager,
+                                                    regex,
+                                                    "libufofilter*.so");
+
+    g_regex_unref (regex);
+    return result;
 }
diff --git a/ufo/ufo-plugin-manager.h b/ufo/ufo-plugin-manager.h
index 76c6570..e28c9bd 100644
--- a/ufo/ufo-plugin-manager.h
+++ b/ufo/ufo-plugin-manager.h
@@ -26,7 +26,7 @@
 
 #include <ufo/ufo-configurable.h>
 #include <ufo/ufo-config.h>
-#include <ufo/ufo-node.h>
+#include <ufo/ufo-task-node.h>
 
 G_BEGIN_DECLS
 
@@ -75,11 +75,21 @@ struct _UfoPluginManagerClass {
 };
 
 UfoPluginManager  * ufo_plugin_manager_new                  (UfoConfig          *config);
-UfoNode           * ufo_plugin_manager_get_task             (UfoPluginManager   *manager,
+gpointer            ufo_plugin_manager_get_plugin           (UfoPluginManager *manager, 
+                                                             const gchar *func_name,
+                                                             const gchar *module_name,
+                                                             GError **error);
+GList             * ufo_plugin_get_all_plugin_names         (UfoPluginManager *manager,
+                                                             const GRegex *filename_regex,
+                                                             const gchar *filename_pattern);
+GType               ufo_plugin_manager_get_type             (void);
+
+
+
+UfoTaskNode       * ufo_plugin_manager_get_task             (UfoPluginManager   *manager,
                                                              const gchar        *name,
                                                              GError            **error);
 GList             * ufo_plugin_manager_get_all_task_names   (UfoPluginManager   *manager);
-GType               ufo_plugin_manager_get_type             (void);
 
 G_END_DECLS
 
diff --git a/ufo/ufo-profiler.c b/ufo/ufo-profiler.c
index 3a9e73e..6cb6800 100644
--- a/ufo/ufo-profiler.c
+++ b/ufo/ufo-profiler.c
@@ -34,9 +34,7 @@
  * @Title: UfoProfiler
  *
  * The #UfoProfiler provides a drop-in replacement for a manual
- * clEnqueueNDRangeKernel() call and tracks any associated events. The amount of
- * profiling can be controlled with an #UfoProfilerLevel when constructing the
- * profiler.
+ * clEnqueueNDRangeKernel() call and tracks any associated events.
  *
  * Each #UfoFilter is assigned a profiler with ufo_profiler_set_profiler() by
  * the managing #UfoBaseScheduler. Filter implementations should call
@@ -54,13 +52,14 @@ G_DEFINE_TYPE(UfoProfiler, ufo_profiler, G_TYPE_OBJECT)
 struct EventRow {
     cl_event    event;
     cl_kernel   kernel;
+    cl_command_queue queue;
 };
 
 struct _UfoProfilerPrivate {
-    UfoProfilerLevel level;
-
     GArray  *event_array;
     GTimer **timers;
+    GList   *trace_events;
+    gboolean trace;
 };
 
 enum {
@@ -68,40 +67,18 @@ enum {
     N_PROPERTIES
 };
 
-/**
- * timer_level:
- *
- * Maps UfoProfilerTimer to a UfoProfilerLevel
- */
-static UfoProfilerLevel timer_level[] = {
-    UFO_PROFILER_LEVEL_IO,      /* TIMER_IO */
-    UFO_PROFILER_LEVEL_CPU,     /* TIMER_CPU */
-    UFO_PROFILER_LEVEL_SYNC,    /* TIMER_FETCH */
-    UFO_PROFILER_LEVEL_SYNC     /* TIMER_RELEASE */
-};
+static GTimer *global_clock = NULL;
 
-/**
- * UfoProfilerLevel:
- * @UFO_PROFILER_LEVEL_NONE: Do not track any profiling information
- * @UFO_PROFILER_LEVEL_IO: Track I/O events
- * @UFO_PROFILER_LEVEL_OPENCL: Track OpenCL events
- * @UFO_PROFILER_LEVEL_CPU: Track general CPU time
- * @UFO_PROFILER_LEVEL_SYNC: Track synchronization wait time
- *
- * Profiling levels that the profiler supports. To set the global profiling
- * level use the #UfoConfiguration:profile-level: property on a
- * #UfoConfiguration object set to the #UfoScheduler.
- */
 
 /**
  * UfoProfilerTimer:
  * @UFO_PROFILER_TIMER_IO: Select I/O timer
  * @UFO_PROFILER_TIMER_CPU: Select CPU timer
+ * @UFO_PROFILER_TIMER_GPU: Select GPU timer
  * @UFO_PROFILER_TIMER_FETCH: Select timer that measures the synchronization
  *  time to fetch data from the queues.
  * @UFO_PROFILER_TIMER_RELEASE: Select timer that measures the synchronization
  *  time to push data to the queues.
- * @UFO_PROFILER_TIMER_CPU: Select CPU timer
  * @UFO_PROFILER_TIMER_LAST: Auxiliary value, do not use.
  *
  * Use these values to select a specific timer when calling
@@ -110,20 +87,15 @@ static UfoProfilerLevel timer_level[] = {
 
 /**
  * ufo_profiler_new:
- * @level: Amount of information that should be tracked by the profiler.
  *
  * Create a profiler object.
  *
  * Return value: A new profiler object.
  */
 UfoProfiler *
-ufo_profiler_new (UfoProfilerLevel level)
+ufo_profiler_new (void)
 {
-    UfoProfiler *profiler;
-
-    profiler = UFO_PROFILER (g_object_new (UFO_TYPE_PROFILER, NULL));
-    profiler->priv->level = level;
-    return profiler;
+    return UFO_PROFILER (g_object_new (UFO_TYPE_PROFILER, NULL));
 }
 
 /**
@@ -151,12 +123,11 @@ ufo_profiler_call (UfoProfiler    *profiler,
 {
     UfoProfilerPrivate *priv;
     cl_event            event;
-    cl_event           *event_loc;
     cl_int              cl_err;
     struct EventRow     row;
 
+    g_return_if_fail (UFO_IS_PROFILER (profiler));
     priv = profiler->priv;
-    event_loc = priv->level & UFO_PROFILER_LEVEL_OPENCL ? &event : NULL;
 
     cl_err = clEnqueueNDRangeKernel (command_queue,
                                      kernel,
@@ -164,14 +135,13 @@ ufo_profiler_call (UfoProfiler    *profiler,
                                      NULL,
                                      global_work_size,
                                      local_work_size,
-                                     0, NULL, event_loc);
+                                     0, NULL, &event);
     UFO_RESOURCES_CHECK_CLERR (cl_err);
 
-    if (priv->level & UFO_PROFILER_LEVEL_OPENCL) {
-        row.event = event;
-        row.kernel = kernel;
-        g_array_append_val (priv->event_array, row);
-    }
+    row.event = event;
+    row.kernel = kernel;
+    row.queue = command_queue;
+    g_array_append_val (priv->event_array, row);
 }
 
 /**
@@ -187,9 +157,7 @@ ufo_profiler_start (UfoProfiler      *profiler,
                     UfoProfilerTimer  timer)
 {
     g_return_if_fail (UFO_IS_PROFILER (profiler));
-
-    if (profiler->priv->level & timer_level[timer])
-        g_timer_continue (profiler->priv->timers[timer]);
+    g_timer_continue (profiler->priv->timers[timer]);
 }
 
 /**
@@ -205,9 +173,93 @@ ufo_profiler_stop (UfoProfiler       *profiler,
                    UfoProfilerTimer   timer)
 {
     g_return_if_fail (UFO_IS_PROFILER (profiler));
+    g_timer_stop (profiler->priv->timers[timer]);
+}
+
+void
+ufo_profiler_trace_event (UfoProfiler *profiler,
+                          const gchar *name,
+                          const gchar *type)
+{
+    UfoTraceEvent *event;
+    gulong timestamp;
+
+    g_return_if_fail (UFO_IS_PROFILER (profiler));
+    if (!profiler->priv->trace)
+        return;
+
+    g_timer_elapsed (global_clock, &timestamp);
+
+    event = g_malloc0 (sizeof(UfoTraceEvent));
+    event->name = name;
+    event->type = type;
+    event->thread_id = g_thread_self ();
+    event->timestamp = (gdouble) timestamp;
+    profiler->priv->trace_events = g_list_append (profiler->priv->trace_events, event);
+}
 
-    if (profiler->priv->level & timer_level[timer])
-        g_timer_stop (profiler->priv->timers[timer]);
+
+void
+ufo_profiler_enable_tracing (UfoProfiler *profiler,
+                             gboolean enable)
+{
+    g_return_if_fail (UFO_IS_PROFILER (profiler));
+    profiler->priv->trace = enable;
+}
+
+/**
+ * ufo_profiler_get_trace_events: (skip)
+ * @profiler: A #UfoProfiler object.
+ *
+ * Get all events recorded with @profiler.
+ *
+ * Returns: (element-type UfoTraceEvent): A list with #UfoTraceEvent objects.
+ */
+GList *
+ufo_profiler_get_trace_events (UfoProfiler *profiler)
+{
+    g_return_val_if_fail (UFO_IS_PROFILER (profiler), NULL);
+    return profiler->priv->trace_events;
+}
+
+static void
+get_time_stamps (cl_event event, gulong *queued, gulong *submitted, gulong *start, gulong *end)
+{
+    UFO_RESOURCES_CHECK_CLERR (clWaitForEvents (1, &event));
+    UFO_RESOURCES_CHECK_CLERR (clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_QUEUED, sizeof (cl_ulong), queued, NULL));
+    UFO_RESOURCES_CHECK_CLERR (clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_SUBMIT, sizeof (cl_ulong), submitted, NULL));
+    UFO_RESOURCES_CHECK_CLERR (clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_START, sizeof (cl_ulong), start, NULL));
+    UFO_RESOURCES_CHECK_CLERR (clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_END, sizeof (cl_ulong), end, NULL));
+}
+
+static gdouble
+gpu_elapsed (UfoProfilerPrivate *priv)
+{
+    struct EventRow *row;
+    gdouble elapsed = 0.0;
+    guint len = priv->event_array->len;
+
+    if (len == 0)
+        return 0.0;
+
+    for (guint i = 0; i < len; i++) {
+        gulong start, end;
+
+        row = &g_array_index (priv->event_array, struct EventRow, i);
+
+        UFO_RESOURCES_CHECK_CLERR (clGetEventInfo (row->event, CL_EVENT_COMMAND_QUEUE,
+                                                   sizeof (cl_command_queue), &row->queue,
+                                                   NULL));
+
+        get_time_stamps (row->event, NULL, NULL, &start, &end);
+
+        if (end < start)
+            elapsed += (gdouble) ((G_MAXULONG - start) + end) * 1e-9;
+        else
+            elapsed += ((gdouble) (end - start)) * 1e-9;
+    }
+
+    return elapsed;
 }
 
 /**
@@ -224,6 +276,10 @@ ufo_profiler_elapsed (UfoProfiler       *profiler,
                       UfoProfilerTimer   timer)
 {
     g_return_val_if_fail (UFO_IS_PROFILER (profiler), 0.0);
+
+    if (timer == UFO_PROFILER_TIMER_GPU)
+        return gpu_elapsed (profiler->priv);
+
     return g_timer_elapsed (profiler->priv->timers[timer], NULL);
 }
 
@@ -239,15 +295,6 @@ get_kernel_name (cl_kernel kernel)
     return s;
 }
 
-static void
-get_time_stamps (cl_event event, gulong *queued, gulong *submitted, gulong *start, gulong *end)
-{
-    clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_QUEUED, sizeof (cl_ulong), queued, NULL);
-    clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_SUBMIT, sizeof (cl_ulong), submitted, NULL);
-    clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_START, sizeof (cl_ulong), start, NULL);
-    clGetEventProfilingInfo (event, CL_PROFILING_COMMAND_END, sizeof (cl_ulong), end, NULL);
-}
-
 /**
  * ufo_profiler_foreach:
  * @profiler: A #UfoProfiler object.
@@ -268,9 +315,6 @@ ufo_profiler_foreach (UfoProfiler    *profiler,
 
     priv = profiler->priv;
 
-    if (priv->level == UFO_PROFILER_LEVEL_NONE)
-        return;
-
     for (guint i = 0; i < priv->event_array->len; i++) {
         cl_command_queue queue;
         gulong queued, submitted, start, end;
@@ -298,25 +342,31 @@ static void
 ufo_profiler_dispose (GObject *object)
 {
     G_OBJECT_CLASS (ufo_profiler_parent_class)->dispose (object);
-    g_message ("UfoProfiler: disposed");
 }
 
 static void
 ufo_profiler_finalize (GObject *object)
 {
     UfoProfilerPrivate *priv;
+    struct EventRow *row;
 
     G_OBJECT_CLASS (ufo_profiler_parent_class)->finalize (object);
     priv = UFO_PROFILER_GET_PRIVATE (object);
 
+    for (guint i = 0; i < priv->event_array->len; i++) {
+        row = &g_array_index (priv->event_array, struct EventRow, i);
+        clReleaseEvent (row->event);
+    }
+
     g_array_free (priv->event_array, TRUE);
 
+    g_list_foreach (priv->trace_events, (GFunc) g_free, NULL);
+    g_list_free (priv->trace_events);
+
     for (guint i = 0; i < UFO_PROFILER_TIMER_LAST; i++)
         g_timer_destroy (priv->timers[i]);
 
     g_free (priv->timers);
-
-    g_message ("UfoProfiler: finalized");
 }
 
 static void
@@ -327,6 +377,9 @@ ufo_profiler_class_init (UfoProfilerClass *klass)
     gobject_class->finalize = ufo_profiler_finalize;
 
     g_type_class_add_private (klass, sizeof (UfoProfilerPrivate));
+
+    if (global_clock == NULL)
+        global_clock = g_timer_new ();
 }
 
 static void
@@ -336,6 +389,8 @@ ufo_profiler_init (UfoProfiler *manager)
 
     manager->priv = priv = UFO_PROFILER_GET_PRIVATE (manager);
     priv->event_array = g_array_sized_new (FALSE, TRUE, sizeof(struct EventRow), 2048);
+    priv->trace_events = NULL;
+    priv->trace = FALSE;
 
     /* Setup timers for all events */
     priv->timers = g_new0 (GTimer *, UFO_PROFILER_TIMER_LAST);
@@ -348,6 +403,4 @@ ufo_profiler_init (UfoProfiler *manager)
         g_timer_reset (timer);
         priv->timers[i] = timer;
     }
-
-    priv->level = UFO_PROFILER_LEVEL_NONE;
 }
diff --git a/ufo/ufo-profiler.h b/ufo/ufo-profiler.h
index 050e251..99253d5 100644
--- a/ufo/ufo-profiler.h
+++ b/ufo/ufo-profiler.h
@@ -71,39 +71,54 @@ struct _UfoProfilerClass {
     GObjectClass parent_class;
 };
 
+/**
+ * UfoTraceEvent:
+ * @name: Name of the event
+ * @type: Type of the event
+ * @thread_id: ID of thread in which the event was issued
+ * @timestamp: Arbitrary timestamp of the event
+ */
+typedef struct {
+    const gchar *name;
+    const gchar *type;
+    gpointer     thread_id;
+    gdouble      timestamp;
+} UfoTraceEvent;
+
 typedef enum {
     UFO_PROFILER_TIMER_IO = 0,
     UFO_PROFILER_TIMER_CPU,
+    UFO_PROFILER_TIMER_GPU,
     UFO_PROFILER_TIMER_FETCH,
     UFO_PROFILER_TIMER_RELEASE,
     UFO_PROFILER_TIMER_LAST,
 } UfoProfilerTimer;
 
-typedef enum {
-    UFO_PROFILER_LEVEL_NONE     = 0,
-    UFO_PROFILER_LEVEL_CPU      = 1 << 0,
-    UFO_PROFILER_LEVEL_OPENCL   = 1 << 1,
-    UFO_PROFILER_LEVEL_IO       = 1 << 2,
-    UFO_PROFILER_LEVEL_SYNC     = 1 << 3
-} UfoProfilerLevel;
-
-UfoProfiler *ufo_profiler_new       (UfoProfilerLevel    level);
-void         ufo_profiler_call      (UfoProfiler        *profiler,
-                                     gpointer            command_queue,
-                                     gpointer            kernel,
-                                     guint               work_dim,
-                                     const gsize        *global_work_size,
-                                     const gsize        *local_work_size);
-void         ufo_profiler_foreach   (UfoProfiler        *profiler,
-                                     UfoProfilerFunc     func,
-                                     gpointer            user_data);
-void         ufo_profiler_start     (UfoProfiler        *profiler,
-                                     UfoProfilerTimer    timer);
-void         ufo_profiler_stop      (UfoProfiler        *profiler,
-                                     UfoProfilerTimer    timer);
-gdouble      ufo_profiler_elapsed   (UfoProfiler        *profiler,
-                                     UfoProfilerTimer    timer);
-GType        ufo_profiler_get_type  (void);
+UfoProfiler *ufo_profiler_new           (void);
+void         ufo_profiler_call          (UfoProfiler        *profiler,
+                                         gpointer            command_queue,
+                                         gpointer            kernel,
+                                         guint               work_dim,
+                                         const gsize        *global_work_size,
+                                         const gsize        *local_work_size);
+void         ufo_profiler_foreach       (UfoProfiler        *profiler,
+                                         UfoProfilerFunc     func,
+                                         gpointer            user_data);
+void         ufo_profiler_start         (UfoProfiler        *profiler,
+                                         UfoProfilerTimer    timer);
+void         ufo_profiler_stop          (UfoProfiler        *profiler,
+                                         UfoProfilerTimer    timer);
+void         ufo_profiler_trace_event   (UfoProfiler        *profiler,
+                                         const gchar        *name,
+                                         const gchar        *type);
+void         ufo_profiler_enable_tracing
+                                        (UfoProfiler        *profiler,
+                                         gboolean            enable);
+GList       *ufo_profiler_get_trace_events
+                                        (UfoProfiler        *profiler);
+gdouble      ufo_profiler_elapsed       (UfoProfiler        *profiler,
+                                         UfoProfilerTimer    timer);
+GType        ufo_profiler_get_type      (void);
 
 G_END_DECLS
 
diff --git a/ufo/ufo-remote-node.c b/ufo/ufo-remote-node.c
index 21d367a..e2e40ab 100644
--- a/ufo/ufo-remote-node.c
+++ b/ufo/ufo-remote-node.c
@@ -17,123 +17,115 @@
  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <zmq.h>
 #include <string.h>
 #include <ufo/ufo-remote-node.h>
+#include <ufo/ufo-messenger-iface.h>
+#include <ufo/ufo-zmq-messenger.h>
+
+#ifdef MPI
+#include <ufo/ufo-mpi-messenger.h>
+#endif
 
 G_DEFINE_TYPE (UfoRemoteNode, ufo_remote_node, UFO_TYPE_NODE)
 
 #define UFO_REMOTE_NODE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_REMOTE_NODE, UfoRemoteNodePrivate))
 
-static void ufo_msg_send (UfoMessage *msg, gpointer socket, gint flags);
-static void receive_ack (gpointer socket);
-
 struct _UfoRemoteNodePrivate {
     gpointer context;
-    gpointer socket;
     guint n_inputs;
-    GMutex *mutex;
+    gboolean terminated;
+    UfoMessenger *msger;
 };
 
 UfoNode *
-ufo_remote_node_new (gpointer zmq_context,
-                     const gchar *address)
+ufo_remote_node_new (const gchar *address)
 {
     UfoRemoteNode *node;
     UfoRemoteNodePrivate *priv;
 
-    g_return_val_if_fail (zmq_context != NULL && address != NULL, NULL);
+    g_return_val_if_fail (address != NULL, NULL);
     node = UFO_REMOTE_NODE (g_object_new (UFO_TYPE_REMOTE_NODE, NULL));
-    priv = node->priv;
-    priv->context = zmq_context;
-    priv->socket = zmq_socket (zmq_context, ZMQ_REQ);
-
-    if (zmq_connect (priv->socket, address) == 0) {
-        g_message ("Connected remote node to `%s' via socket=%p",
-                   address,
-                   priv->socket);
-        return UFO_NODE (node);
-    }
-    else {
-        g_warning ("Could not connect to `%s': %s",
-                   address,
-                   zmq_strerror (errno));
-        g_object_unref (node);
-        return NULL;
-    }
+    priv = UFO_REMOTE_NODE_GET_PRIVATE (node);
+
+#ifdef MPI
+    priv->msger = UFO_MESSENGER (ufo_mpi_messenger_new ());
+#else
+    priv->msger = UFO_MESSENGER (ufo_zmq_messenger_new ());
+#endif
+
+    gchar *addr = g_strdup (address);
+    ufo_messenger_connect (priv->msger, addr, UFO_MESSENGER_CLIENT);
+    g_free(addr);
+
+    return UFO_NODE (node);
 }
 
 guint
 ufo_remote_node_get_num_gpus (UfoRemoteNode *node)
 {
-    UfoRemoteNodePrivate *priv;
-    UfoMessage request;
-    UfoMessage result;
-    zmq_msg_t reply;
-
     g_return_val_if_fail (UFO_IS_REMOTE_NODE (node), 0);
-    priv = node->priv;
-
-    g_mutex_lock (priv->mutex);
 
-    request.type = UFO_MESSAGE_GET_NUM_DEVICES;
-    ufo_msg_send (&request, priv->socket, 0);
+    UfoRemoteNodePrivate *priv;
+    UfoMessage *request = ufo_message_new (UFO_MESSAGE_GET_NUM_DEVICES, 0);
 
-    zmq_msg_init (&reply);
-    zmq_msg_recv (&reply, priv->socket, 0);
-    memcpy (&result, zmq_msg_data (&reply), sizeof (UfoMessage));
-    zmq_msg_close (&reply);
+    priv = node->priv;
 
-    g_mutex_unlock (priv->mutex);
+    UfoMessage *result;
+    result = ufo_messenger_send_blocking (priv->msger, request, NULL);
+    guint n_devices = * (guint16 *) result->data;
 
-    return result.d.n_devices;
+    ufo_message_free (request);
+    ufo_message_free (result);
+    g_assert (n_devices < 32);
+    return n_devices;
 }
 
 void
 ufo_remote_node_request_setup (UfoRemoteNode *node)
 {
-    UfoRemoteNodePrivate *priv;
-    UfoMessage request;
+    // TODO setup isn't in use, remove it
+    //g_assert (FALSE);
 
-    g_return_if_fail (UFO_IS_REMOTE_NODE (node));
-
-    priv = node->priv;
-    request.type = UFO_MESSAGE_SETUP;
-
-    g_mutex_lock (priv->mutex);
+    // g_return_if_fail (UFO_IS_REMOTE_NODE (node));
+    // UfoRemoteNodePrivate *priv = UFO_REMOTE_NODE_GET_PRIVATE (node);
 
-    ufo_msg_send (&request, priv->socket, 0);
-    receive_ack (priv->socket);
-
-    g_mutex_unlock (priv->mutex);
+    // UfoMessage *request;
+    // request = ufo_message_new (UFO_MESSAGE_SETUP, 0);
+    // ufo_message_send_blocking (request);
+    // ufo_message_free (request);
 }
 
 void
 ufo_remote_node_send_json (UfoRemoteNode *node,
-                           const gchar *json,
-                           gsize size)
+                           UfoRemoteMode mode,
+                           const gchar *json)
 {
     UfoRemoteNodePrivate *priv;
-    UfoMessage request;
-    zmq_msg_t json_msg;
+    UfoMessage *request;
+    UfoMessageType type;
+    guint64 size;
 
     g_return_if_fail (UFO_IS_REMOTE_NODE (node));
 
     priv = node->priv;
-    request.type = UFO_MESSAGE_TASK_JSON;
-
-    g_mutex_lock (priv->mutex);
-
-    ufo_msg_send (&request, priv->socket, ZMQ_SNDMORE);
 
-    zmq_msg_init_size (&json_msg, size);
-    memcpy (zmq_msg_data (&json_msg), json, size);
-    zmq_msg_send (&json_msg, priv->socket, 0);
-    zmq_msg_close (&json_msg);
+    switch (mode) {
+        case UFO_REMOTE_MODE_STREAM:
+            type = UFO_MESSAGE_STREAM_JSON;
+            break;
+        case UFO_REMOTE_MODE_REPLICATE:
+            type = UFO_MESSAGE_REPLICATE_JSON;
+            break;
+        default:
+            g_warning ("Don't understand UfoRemoteMode");
+            type = 0;
+    }
 
-    receive_ack (priv->socket);
+    size = (guint64) strlen (json);
+    request = ufo_message_new (type, size);
 
-    g_mutex_unlock (priv->mutex);
+    memcpy (request->data, json, size);
+    ufo_messenger_send_blocking (priv->msger, request, NULL);
 }
 
 void
@@ -143,41 +135,33 @@ ufo_remote_node_get_structure (UfoRemoteNode *node,
                                UfoTaskMode *mode)
 {
     UfoRemoteNodePrivate *priv;
-    UfoMessage request;
-    UfoMessage *header;
-    zmq_msg_t header_msg;
-    zmq_msg_t payload_msg;
-    UfoInputParam *in_param;
-
-    g_return_if_fail (UFO_IS_REMOTE_NODE (node));
+    UfoMessage *request, *response;
 
     priv = node->priv;
-    *mode = UFO_TASK_MODE_SINGLE;
-    request.type = UFO_MESSAGE_GET_STRUCTURE;
 
-    g_mutex_lock (priv->mutex);
+    struct _Structure {
+        guint16 n_inputs;
+        guint16 n_dims;
+    } msg_data;
 
-    ufo_msg_send (&request, priv->socket, 0);
+    g_return_if_fail (UFO_IS_REMOTE_NODE (node));
+    *mode = UFO_TASK_MODE_PROCESSOR;
 
-    /* Receive header */
-    zmq_msg_init (&header_msg);
-    zmq_msg_recv (&header_msg, priv->socket, 0);
-    header = (UfoMessage *) zmq_msg_data (&header_msg);
 
-    /* Receive payload */
-    zmq_msg_init (&payload_msg);
-    zmq_msg_recv (&payload_msg, priv->socket, 0);
-    in_param = (UfoInputParam *) zmq_msg_data (&payload_msg);
+    request = ufo_message_new (UFO_MESSAGE_GET_STRUCTURE, 0);
+    response = ufo_messenger_send_blocking (priv->msger, request, NULL);
+    g_assert (response->data_size == sizeof (struct _Structure));
 
-    priv->n_inputs = header->d.n_inputs;
-    *n_inputs = header->d.n_inputs;
-    *in_params = g_new0 (UfoInputParam, 1);
-    (*in_params)[0].n_dims = in_param->n_dims;
+    msg_data = *(struct _Structure *) response->data;
+
+    priv->n_inputs = msg_data.n_inputs;
+    *n_inputs = msg_data.n_inputs;
 
-    zmq_msg_close (&header_msg);
-    zmq_msg_close (&payload_msg);
+    *in_params = g_new0 (UfoInputParam, 1);
+    (*in_params)[0].n_dims = msg_data.n_dims;
 
-    g_mutex_unlock (priv->mutex);
+    ufo_message_free (request);
+    ufo_message_free (response);
 }
 
 void
@@ -185,48 +169,51 @@ ufo_remote_node_send_inputs (UfoRemoteNode *node,
                              UfoBuffer **inputs)
 {
     UfoRemoteNodePrivate *priv;
-    UfoMessage request;
+    UfoMessage *request;
 
     g_return_if_fail (UFO_IS_REMOTE_NODE (node));
 
     priv = node->priv;
-    request.type = UFO_MESSAGE_SEND_INPUTS;
-
-    g_mutex_lock (priv->mutex);
-
-    ufo_msg_send (&request, priv->socket, ZMQ_SNDMORE);
 
     /*
      * For each of the input data items send two frames: the first one contains
      * the size as an UfoRequisition struct and the second one the raw byte
      * data.
      */
-    for (guint i = 0; i < priv->n_inputs; i++) {
+    struct _Header {
         UfoRequisition requisition;
-        zmq_msg_t requisition_msg;
-        zmq_msg_t data_msg;
-        gsize size;
-        gint flags;
+        guint64 buffer_size;
+    };
 
-        ufo_buffer_get_requisition (inputs[i], &requisition);
-        size = ufo_buffer_get_size (inputs[i]);
+    // determine our total message size
+    guint64 size = 0;
 
-        zmq_msg_init_size (&requisition_msg, sizeof (UfoRequisition));
-        zmq_msg_init_size (&data_msg, size);
+    for (guint i = 0; i < priv->n_inputs; i++) {
+        guint64 buffer_size = ufo_buffer_get_size (inputs[i]);
+        size += buffer_size;
+    }
 
-        memcpy (zmq_msg_data (&requisition_msg), &requisition, sizeof (UfoRequisition));
-        memcpy (zmq_msg_data (&data_msg), ufo_buffer_get_host_array (inputs[i], NULL), size);
+    gpointer buffer = g_malloc (priv->n_inputs * sizeof (struct _Header) + size);
 
-        flags = i == priv->n_inputs - 1 ? 0 : ZMQ_SNDMORE;
-        zmq_msg_send (&requisition_msg, priv->socket, ZMQ_SNDMORE);
-        zmq_msg_send (&data_msg, priv->socket, flags);
+    char *base = buffer;
 
-        zmq_msg_close (&requisition_msg);
-        zmq_msg_close (&data_msg);
+    for (guint i = 0; i < priv->n_inputs; i++) {
+        struct _Header *header = g_new0 (struct _Header, 1);
+        ufo_buffer_get_requisition (inputs[i], &header->requisition);
+        header->buffer_size = (guint64) ufo_buffer_get_size (inputs[i]);
+
+        memcpy (base, header, sizeof (struct _Header));
+        base += sizeof (struct _Header);
+        memcpy (base, ufo_buffer_get_host_array (inputs[i], NULL), header->buffer_size);
+        base += header->buffer_size;
     }
 
-    receive_ack (priv->socket);
-    g_mutex_unlock (priv->mutex);
+    request = ufo_message_new (UFO_MESSAGE_SEND_INPUTS, size);
+    g_free (request->data);
+    request->data = buffer;
+    // send as a single message
+    ufo_messenger_send_blocking (priv->msger, request, NULL);
+
 }
 
 void
@@ -234,31 +221,23 @@ ufo_remote_node_get_result (UfoRemoteNode *node,
                             UfoBuffer *buffer)
 {
     UfoRemoteNodePrivate *priv;
-    UfoMessage request;
-    zmq_msg_t reply_msg;
+    UfoMessage *request, *response;
     gpointer host_array;
 
     g_return_if_fail (UFO_IS_REMOTE_NODE (node));
 
     priv = node->priv;
-    request.type = UFO_MESSAGE_GET_RESULT;
-
-    g_mutex_lock (priv->mutex);
+    request = ufo_message_new (UFO_MESSAGE_GET_RESULT, 0);
+    response = ufo_messenger_send_blocking (priv->msger, request, NULL);
 
-    ufo_msg_send (&request, priv->socket, 0);
-
-    /* Get the remote data and put it into our buffer */
-    zmq_msg_init (&reply_msg);
-    zmq_msg_recv (&reply_msg, priv->socket, 0);
-
-    ufo_buffer_discard_location (buffer, UFO_LOCATION_DEVICE);
+    ufo_buffer_discard_location (buffer);
     host_array = ufo_buffer_get_host_array (buffer, NULL);
-    g_assert (ufo_buffer_get_size (buffer) == zmq_msg_size (&reply_msg));
-    memcpy (host_array, zmq_msg_data (&reply_msg), ufo_buffer_get_size (buffer));
+    g_assert (ufo_buffer_get_size (buffer) == response->data_size);
 
-    zmq_msg_close (&reply_msg);
+    memcpy (host_array, response->data, ufo_buffer_get_size (buffer));
 
-    g_mutex_unlock (priv->mutex);
+    ufo_message_free (request);
+    ufo_message_free (response);
 }
 
 void
@@ -266,74 +245,57 @@ ufo_remote_node_get_requisition (UfoRemoteNode *node,
                                  UfoRequisition *requisition)
 {
     UfoRemoteNodePrivate *priv;
-    UfoMessage request;
-    zmq_msg_t reply_msg;
+    UfoMessage *request, *response;
 
     g_return_if_fail (UFO_IS_REMOTE_NODE (node));
 
     priv = node->priv;
-    request.type = UFO_MESSAGE_GET_REQUISITION;
-
-    g_mutex_lock (priv->mutex);
-
-    ufo_msg_send (&request, priv->socket, 0);
+    request = ufo_message_new (UFO_MESSAGE_GET_REQUISITION, 0);
+    response = ufo_messenger_send_blocking (priv->msger, request, NULL);
 
-    zmq_msg_init (&reply_msg);
-    zmq_msg_recv (&reply_msg, priv->socket, 0);
-    g_assert (zmq_msg_size (&reply_msg) >= sizeof (UfoRequisition));
-    memcpy (requisition, zmq_msg_data (&reply_msg), sizeof (UfoRequisition));
-    zmq_msg_close (&reply_msg);
+    g_assert (response->data_size == sizeof (UfoRequisition));
+    memcpy (requisition, response->data, sizeof (UfoRequisition));
 
-    g_mutex_unlock (priv->mutex);
+    ufo_message_free(request);
+    ufo_message_free(response);
 }
 
 static void
-cleanup_remote (gpointer socket)
+cleanup_remote (UfoRemoteNodePrivate *priv)
 {
-    UfoMessage request;
-
-    request.type = UFO_MESSAGE_CLEANUP;
-
-    ufo_msg_send (&request, socket, 0);
-    receive_ack (socket);
+    UfoMessage *request = ufo_message_new (UFO_MESSAGE_CLEANUP, 0);
+    ufo_messenger_send_blocking (priv->msger, request, NULL);
+    ufo_message_free (request);
 }
 
-static void
-ufo_msg_send (UfoMessage *msg,
-              gpointer socket,
-              gint flags)
+void
+ufo_remote_node_terminate (UfoRemoteNode *node)
 {
-    zmq_msg_t request;
+    UfoRemoteNodePrivate *priv = UFO_REMOTE_NODE_GET_PRIVATE (node);
 
-    zmq_msg_init_size (&request, sizeof (UfoMessage));
-    memcpy (zmq_msg_data (&request), msg, sizeof (UfoMessage));
-    zmq_msg_send (&request, socket, flags);
-    zmq_msg_close (&request);
-}
+    priv->terminated = TRUE;
+    cleanup_remote (priv);
 
-static void
-receive_ack (gpointer socket)
-{
-    zmq_msg_t reply_msg;
+    UfoMessage *request;
+
+    g_return_if_fail (UFO_IS_REMOTE_NODE (node));
+
+    priv = node->priv;
+    request = ufo_message_new (UFO_MESSAGE_TERMINATE, 0);
+    ufo_messenger_send_blocking (priv->msger, request, NULL);
 
-    zmq_msg_init (&reply_msg);
-    zmq_msg_recv (&reply_msg, socket, 0);
-    zmq_msg_close (&reply_msg);
+    ufo_messenger_disconnect (priv->msger);
+    return; 
 }
 
 static void
 ufo_remote_node_dispose (GObject *object)
 {
-    UfoRemoteNodePrivate *priv;
-
-    priv = UFO_REMOTE_NODE_GET_PRIVATE (object);
-
-    if (priv->socket != NULL) {
-        cleanup_remote (priv->socket);
+    UfoRemoteNodePrivate *priv = UFO_REMOTE_NODE_GET_PRIVATE (object);
 
-        g_debug ("Close socket=%p", priv->socket);
-        zmq_close (priv->socket);
-        priv->socket = NULL;
+    if (!priv->terminated) {
+        cleanup_remote (priv);
+        ufo_messenger_disconnect (priv->msger);
     }
 
     G_OBJECT_CLASS (ufo_remote_node_parent_class)->dispose (object);
@@ -342,11 +304,6 @@ ufo_remote_node_dispose (GObject *object)
 static void
 ufo_remote_node_finalize (GObject *object)
 {
-    UfoRemoteNodePrivate *priv;
-
-    priv = UFO_REMOTE_NODE_GET_PRIVATE (object);
-    g_mutex_free (priv->mutex);
-
     G_OBJECT_CLASS (ufo_remote_node_parent_class)->finalize (object);
 }
 
@@ -367,8 +324,6 @@ ufo_remote_node_init (UfoRemoteNode *self)
 {
     UfoRemoteNodePrivate *priv;
     self->priv = priv = UFO_REMOTE_NODE_GET_PRIVATE (self);
-    priv->context = NULL;
-    priv->socket = NULL;
     priv->n_inputs = 0;
-    priv->mutex = g_mutex_new ();
+    priv->terminated = FALSE;
 }
diff --git a/ufo/ufo-remote-node.h b/ufo/ufo-remote-node.h
index ad2bf63..22958af 100644
--- a/ufo/ufo-remote-node.h
+++ b/ufo/ufo-remote-node.h
@@ -27,6 +27,7 @@
 #include <ufo/ufo-node.h>
 #include <ufo/ufo-buffer.h>
 #include <ufo/ufo-task-iface.h>
+#include <ufo/ufo-messenger-iface.h>
 
 G_BEGIN_DECLS
 
@@ -40,7 +41,6 @@ G_BEGIN_DECLS
 typedef struct _UfoRemoteNode           UfoRemoteNode;
 typedef struct _UfoRemoteNodeClass      UfoRemoteNodeClass;
 typedef struct _UfoRemoteNodePrivate    UfoRemoteNodePrivate;
-typedef struct _UfoMessage              UfoMessage;
 
 /**
  * UfoRemoteNode:
@@ -66,55 +66,25 @@ struct _UfoRemoteNodeClass {
 };
 
 /**
- * UfoMessageType: (skip)
- * @UFO_MESSAGE_TASK_JSON: insert
- * @UFO_MESSAGE_GET_NUM_DEVICES: insert
- * @UFO_MESSAGE_SETUP: insert
- * @UFO_MESSAGE_GET_STRUCTURE: insert
- * @UFO_MESSAGE_STRUCTURE: insert
- * @UFO_MESSAGE_GET_REQUISITION: insert
- * @UFO_MESSAGE_REQUISITION: insert
- * @UFO_MESSAGE_SEND_INPUTS: insert
- * @UFO_MESSAGE_GET_RESULT: insert
- * @UFO_MESSAGE_RESULT: insert
- * @UFO_MESSAGE_CLEANUP: insert
- * @UFO_MESSAGE_ACK: insert
+ * UfoRemoteMode:
+ * @UFO_REMOTE_MODE_STREAM: Expand task graph and execute only sub-branches
+ *  remotely.
+ * @UFO_REMOTE_MODE_REPLICATE: Replicate the entire task graph and execute it
+ *  remotely.
  */
 typedef enum {
-    UFO_MESSAGE_TASK_JSON = 0,
-    UFO_MESSAGE_GET_NUM_DEVICES,
-    UFO_MESSAGE_SETUP,
-    UFO_MESSAGE_GET_STRUCTURE,
-    UFO_MESSAGE_STRUCTURE,
-    UFO_MESSAGE_GET_REQUISITION,
-    UFO_MESSAGE_REQUISITION,
-    UFO_MESSAGE_SEND_INPUTS,
-    UFO_MESSAGE_GET_RESULT,
-    UFO_MESSAGE_RESULT,
-    UFO_MESSAGE_CLEANUP,
-    UFO_MESSAGE_ACK
-} UfoMessageType;
+    UFO_REMOTE_MODE_STREAM,
+    UFO_REMOTE_MODE_REPLICATE
+} UfoRemoteMode;
 
-/**
- * UfoMessage: (skip)
- * @type: Type of the wire message
- */
-struct _UfoMessage {
-    UfoMessageType  type;
 
-    union {
-        guint16 n_inputs;
-        guint16 n_devices;
-    } d;
-};
 
-UfoNode  *ufo_remote_node_new               (gpointer     zmq_context,
-                                             const gchar    *address);
+UfoNode  *ufo_remote_node_new               (const gchar    *address);
 guint     ufo_remote_node_get_num_gpus      (UfoRemoteNode  *node);
 void      ufo_remote_node_request_setup     (UfoRemoteNode  *node);
 void      ufo_remote_node_send_json         (UfoRemoteNode  *node,
-                                             const gchar    *json,
-                                             gsize           size);
+                                             UfoRemoteMode   mode,
+                                             const gchar    *json);
 void      ufo_remote_node_get_structure     (UfoRemoteNode  *node,
                                              guint          *n_inputs,
                                              UfoInputParam **in_params,
@@ -126,6 +96,7 @@ void      ufo_remote_node_get_result        (UfoRemoteNode  *node,
 void      ufo_remote_node_get_requisition   (UfoRemoteNode  *node,
                                              UfoRequisition *requisition);
 void      ufo_remote_node_cleanup           (UfoRemoteNode  *node);
+void      ufo_remote_node_terminate         (UfoRemoteNode  *node);
 GType     ufo_remote_node_get_type          (void);
 
 G_END_DECLS
diff --git a/ufo/ufo-resources.c b/ufo/ufo-resources.c
index d8295e7..83f360f 100644
--- a/ufo/ufo-resources.c
+++ b/ufo/ufo-resources.c
@@ -20,6 +20,7 @@
 #include "config.h"
 
 #include <glib.h>
+#include <gio/gio.h>
 #include <stdio.h>
 #ifdef __APPLE__
 #include <OpenCL/cl.h>
@@ -39,13 +40,18 @@
  * kernels from text files.
  */
 
+static void ufo_resources_initable_iface_init (GInitableIface *iface);
+
 G_DEFINE_TYPE_WITH_CODE (UfoResources, ufo_resources, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (UFO_TYPE_CONFIGURABLE, NULL))
+                         G_IMPLEMENT_INTERFACE (UFO_TYPE_CONFIGURABLE, NULL)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                ufo_resources_initable_iface_init))
 
 #define UFO_RESOURCES_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_RESOURCES, UfoResourcesPrivate))
 
 /**
  * UfoResourcesError:
+ * @UFO_RESOURCES_ERROR_GENERAL: General resource problems
  * @UFO_RESOURCES_ERROR_LOAD_PROGRAM: Could not load the OpenCL file
  * @UFO_RESOURCES_ERROR_CREATE_PROGRAM: Could not create a program from
  *      the sources
@@ -62,20 +68,21 @@ ufo_resources_error_quark (void)
 }
 
 struct _UfoResourcesPrivate {
+    GError          *construct_error;
     UfoConfig       *config;
 
-    cl_uint          num_platforms;
-    cl_platform_id  *opencl_platforms;
-    cl_context       opencl_context;
-    cl_uint         *num_devices;       /**< Number of OpenCL devices per platform id */
-    cl_device_id   **opencl_devices;    /**< Array of OpenCL devices per platform id */
-    cl_command_queue *command_queues;    /**< Array of command queues per device */
+    cl_platform_id   platform;
+    cl_context       context;
+    cl_uint          n_devices;         /**< Number of OpenCL devices per platform id */
+    cl_device_id     *devices;          /**< Array of OpenCL devices per platform id */
+    cl_command_queue *command_queues;   /**< Array of command queues per device */
 
+    GList       *include_paths;         /**< List of include paths for kernel includes >*/
     GList       *kernel_paths;          /**< Colon-separated string with paths to kernel files */
-    GHashTable  *opencl_programs;       /**< Map from filename to cl_program */
-    GList       *opencl_kernels;
-    GString     *opencl_build_options;
-    GString     *include_paths;         /**< List of include paths "-I/foo/bar" built from added paths */
+    GHashTable  *kernel_cache;
+    GList       *programs;
+    GList       *kernels;
+    GString     *build_opts;
 };
 
 enum {
@@ -162,7 +169,7 @@ ufo_resources_clerr (int error)
 }
 
 static gchar *
-resources_load_opencl_program (const gchar *filename)
+read_file (const gchar *filename)
 {
     FILE *fp = fopen (filename, "r");
 
@@ -191,19 +198,22 @@ resources_load_opencl_program (const gchar *filename)
 }
 
 static void
-resources_release_kernel (cl_kernel kernel)
+release_kernel (cl_kernel kernel)
 {
+    g_debug ("Release kernel=%p", (gpointer) kernel);
     UFO_RESOURCES_CHECK_CLERR (clReleaseKernel (kernel));
 }
 
 static void
-resources_release_program (cl_program program)
+release_program (cl_program program)
 {
+    g_debug ("Release program=%p", (gpointer) program);
     UFO_RESOURCES_CHECK_CLERR (clReleaseProgram (program));
 }
 
 static gchar *
-resources_find_path (UfoResourcesPrivate *priv, const gchar *filename)
+lookup_kernel_path (UfoResourcesPrivate *priv,
+                  const gchar *filename)
 {
     /* Check first if filename is already a path */
     if (g_path_is_absolute (filename)) {
@@ -213,43 +223,287 @@ resources_find_path (UfoResourcesPrivate *priv, const gchar *filename)
             return NULL;
     }
 
-    /* If it is not a path, search in all paths that were added */
-    GList *elem = g_list_first (priv->kernel_paths);
+    for (GList *it = g_list_first (priv->kernel_paths); it != NULL; it = g_list_next (it)) {
+        gchar *path;
 
-    while (elem != NULL) {
-        gchar *path = g_strdup_printf ("%s%c%s", (gchar *) elem->data, G_DIR_SEPARATOR, filename);
+        path = g_build_filename ((gchar *) it->data, filename, NULL);
 
         if (g_file_test (path, G_FILE_TEST_EXISTS))
             return path;
 
         g_free (path);
-        elem = g_list_next (elem);
     }
 
     return NULL;
 }
 
+static gboolean
+platform_has_gpus (cl_platform_id platform)
+{
+    cl_uint n_devices = 0;
+    cl_int err;
+
+    err = clGetDeviceIDs (platform,
+                          CL_DEVICE_TYPE_GPU,
+                          0, NULL, &n_devices);
+
+    if (err != CL_DEVICE_NOT_FOUND)
+        UFO_RESOURCES_CHECK_CLERR (err);
+
+    return n_devices > 0;
+}
+
+static cl_platform_id
+get_preferably_gpu_based_platform (void)
+{
+    cl_platform_id *platforms;
+    cl_uint n_platforms;
+    cl_platform_id candidate = 0;
+
+    UFO_RESOURCES_CHECK_CLERR (clGetPlatformIDs (0, NULL, &n_platforms));
+    platforms = g_malloc0 (n_platforms * sizeof (cl_platform_id));
+    UFO_RESOURCES_CHECK_CLERR (clGetPlatformIDs (n_platforms, platforms, NULL));
+
+    if (n_platforms > 0)
+        candidate = platforms[0];
+
+    for (guint i = 0; i < n_platforms; i++) {
+        if (platform_has_gpus (platforms[i])) {
+            candidate = platforms[i];
+            break;
+        }
+    }
+
+    g_free (platforms);
+    return candidate;
+}
+
+static gboolean
+platform_vendor_has_prefix (cl_platform_id platform,
+                            const gchar *prefix)
+{
+    gboolean has_prefix;
+    gchar *str;
+    gsize size;
+
+    UFO_RESOURCES_CHECK_CLERR (clGetPlatformInfo (platform, CL_PLATFORM_VENDOR, 0, NULL, &size));
+    str = g_malloc0 (size);
+
+    UFO_RESOURCES_CHECK_CLERR (clGetPlatformInfo (platform, CL_PLATFORM_VENDOR, size, str, NULL));
+    has_prefix = g_str_has_prefix (str, prefix);
+
+    g_free (str);
+    return has_prefix;
+}
+
+static void
+add_vendor_to_build_opts (GString *opts,
+                          cl_platform_id platform)
+{
+    if (platform_vendor_has_prefix (platform, "NVIDIA"))
+        g_string_append (opts, "-cl-nv-verbose -DVENDOR=NVIDIA");
+
+    if (platform_vendor_has_prefix (platform, "Advanced Micro Devices"))
+        g_string_append (opts, "-DVENDOR=AMD");
+}
+
+static cl_device_type
+get_device_type (UfoResourcesPrivate *priv)
+{
+    if (priv->config == NULL)
+        return CL_DEVICE_TYPE_ALL;
+
+    switch (ufo_config_get_device_type (priv->config)) {
+        case UFO_DEVICE_CPU:
+            return CL_DEVICE_TYPE_CPU;
+        case UFO_DEVICE_GPU:
+            return CL_DEVICE_TYPE_GPU;
+        case UFO_DEVICE_ALL:
+            return CL_DEVICE_TYPE_ALL;
+    }
+
+    return CL_DEVICE_TYPE_ALL;
+}
+
+static void
+restrict_to_gpu_subset (UfoResourcesPrivate *priv)
+{
+    /*
+     * Selects a single GPU which can be set via the UFO_USE_GPU
+     * environment variable, even if more GPUs are available. The specifc GPU
+     * is selected via the integer value of UFO_USE_GPU (index starts at 1).
+     * Used for debugging and evaluation.
+     */
+
+    const gchar* env_gpu = g_getenv ("UFO_USE_GPU");
+    if (env_gpu == NULL || g_strcmp0 (env_gpu, "") == 0)
+        return;
+
+    guint device_index = (guint) g_ascii_strtoull (env_gpu, NULL, 0);
+    if (device_index == 0) {
+        g_error ("Unrecognized format for env var UFO_USE_GPU");
+        return;
+    }
+    if (device_index > priv->n_devices) {
+        g_error ("Can't select UFO_USE_GPU=%d gpus as it exceeds number of available devices", device_index);
+        return;
+    }
+
+    // TODO allow restriction to real subset, like 1,3,5 etc.
+    cl_device_id *devices_subset = g_malloc0 (1 * sizeof (cl_device_id));
+    devices_subset[0] = priv->devices[device_index - 1];
+    g_free (priv->devices);
+    priv->devices = devices_subset;
+    priv->n_devices = 1;
+}
+
+static gboolean
+initialize_opencl (UfoResourcesPrivate *priv,
+                   GError **error)
+{
+    cl_int errcode = CL_SUCCESS;
+    cl_device_type device_type;
+    cl_command_queue_properties queue_properties = CL_QUEUE_PROFILING_ENABLE;
+
+    priv->platform = get_preferably_gpu_based_platform ();
+    add_vendor_to_build_opts (priv->build_opts, priv->platform);
+    device_type = get_device_type (priv);
+
+    errcode = clGetDeviceIDs (priv->platform, device_type, 0, NULL, &priv->n_devices);
+    UFO_RESOURCES_CHECK_AND_SET (errcode, error);
+
+    if (errcode != CL_SUCCESS)
+        return FALSE;
+
+    priv->devices = g_malloc0 (priv->n_devices * sizeof (cl_device_id));
+
+    errcode = clGetDeviceIDs (priv->platform, device_type, priv->n_devices, priv->devices, NULL);
+    UFO_RESOURCES_CHECK_AND_SET (errcode, error);
+
+    if (errcode != CL_SUCCESS)
+        return FALSE;
+
+    restrict_to_gpu_subset (priv);
+
+    priv->context = clCreateContext (NULL,
+                                     priv->n_devices, priv->devices,
+                                     NULL, NULL, &errcode);
+
+    UFO_RESOURCES_CHECK_AND_SET (errcode, error);
+
+    if (errcode != CL_SUCCESS)
+        return FALSE;
+
+    priv->command_queues = g_malloc0 (priv->n_devices * sizeof (cl_command_queue));
+
+    for (guint i = 0; i < priv->n_devices; i++) {
+        priv->command_queues[i] = clCreateCommandQueue (priv->context,
+                                                        priv->devices[i],
+                                                        queue_properties, &errcode);
+        UFO_RESOURCES_CHECK_AND_SET (errcode, error);
+
+        if (errcode != CL_SUCCESS)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
 /**
  * ufo_resources_new:
  * @config: A #UfoConfiguration object or %NULL
+ * @error: Location of a #GError or %NULL
  *
  * Create a new #UfoResources instance.
  *
  * Returns: (transfer none): A new #UfoResources
  */
 UfoResources *
-ufo_resources_new (UfoConfig *config)
+ufo_resources_new (UfoConfig *config,
+                   GError **error)
+{
+    return g_initable_new (UFO_TYPE_RESOURCES, NULL, error,
+                           "config", config,
+                           NULL);
+}
+
+static gchar *
+escape_device_name (gchar *name)
+{
+    gchar *tmp = name;
+
+    while (*tmp) {
+        gchar c = *tmp;
+
+        if (c == ' ')
+            *tmp = '_';
+        else
+            *tmp = g_ascii_toupper (c);
+
+        tmp++;
+    }
+
+    return name;
+}
+
+static void
+append_include_path (const gchar *path,
+                     GString *str)
+{
+    g_string_append_printf (str, " -I%s", path);
+}
+
+static gchar *
+get_device_build_options (UfoResourcesPrivate *priv,
+                          guint device_index,
+                          const gchar *additional)
 {
-    return UFO_RESOURCES (g_object_new (UFO_TYPE_RESOURCES,
-                                        "config", config,
-                                        NULL));
+    GString *opts;
+    gsize size;
+    gchar *name;
+
+    g_assert (device_index < priv->n_devices);
+
+    opts = g_string_new (priv->build_opts->str);
+
+    if (additional != NULL)
+        g_string_append (opts, additional);
+
+    UFO_RESOURCES_CHECK_CLERR (clGetDeviceInfo (priv->devices[device_index],
+                                                CL_DEVICE_NAME, 0, NULL, &size));
+    name = g_malloc0 (size);
+
+    UFO_RESOURCES_CHECK_CLERR (clGetDeviceInfo (priv->devices[device_index],
+                                                CL_DEVICE_NAME, size, name, NULL));
+
+    g_string_append_printf (opts, " -DDEVICE=%s", escape_device_name (name));
+    g_free (name);
+
+    g_list_foreach (priv->include_paths, (GFunc) append_include_path, opts);
+
+    return g_string_free (opts, FALSE);
 }
 
 static void
-append_include_path (gchar *path,
-                     GString *directive)
+handle_build_error (cl_program program,
+                    cl_device_id device,
+                    cl_int errcode,
+                    GError **error)
 {
-    g_string_append_printf (directive, " -I%s", path);
+    const gsize LOG_SIZE = 4096;
+    gchar *log;
+
+    g_set_error (error,
+                 UFO_RESOURCES_ERROR,
+                 UFO_RESOURCES_ERROR_BUILD_PROGRAM,
+                 "Failed to build OpenCL program: %s", ufo_resources_clerr (errcode));
+
+    log = g_malloc0 (LOG_SIZE * sizeof (char));
+
+    UFO_RESOURCES_CHECK_CLERR (clGetProgramBuildInfo (program, device, CL_PROGRAM_BUILD_LOG,
+                                                      LOG_SIZE, log, NULL));
+    g_print ("\n=== Build log ===%s\n\n", log);
+    g_free (log);
 }
 
 static cl_program
@@ -259,10 +513,10 @@ add_program_from_source (UfoResourcesPrivate *priv,
                          GError **error)
 {
     cl_program program;
-    gchar *build_options = NULL;
     cl_int errcode = CL_SUCCESS;
+    gchar *build_options;
 
-    program = clCreateProgramWithSource (priv->opencl_context,
+    program = clCreateProgramWithSource (priv->context,
                                          1, &source, NULL, &errcode);
 
     if (errcode != CL_SUCCESS) {
@@ -273,99 +527,75 @@ add_program_from_source (UfoResourcesPrivate *priv,
         return NULL;
     }
 
-    if (options != NULL)
-        build_options = g_strdup_printf ("%s %s %s", priv->opencl_build_options->str, priv->include_paths->str, options);
-    else
-        build_options = g_strdup_printf ("%s %s", priv->opencl_build_options->str, priv->include_paths->str);
+    build_options = get_device_build_options (priv, 0, options);
 
     errcode = clBuildProgram (program,
-                              priv->num_devices[0],
-                              priv->opencl_devices[0],
+                              priv->n_devices, priv->devices,
                               build_options,
                               NULL, NULL);
 
-    g_free (build_options);
-
     if (errcode != CL_SUCCESS) {
-        g_set_error (error,
-                     UFO_RESOURCES_ERROR,
-                     UFO_RESOURCES_ERROR_BUILD_PROGRAM,
-                     "Failed to build OpenCL program: %s", ufo_resources_clerr (errcode));
-
-        const gsize LOG_SIZE = 4096;
-        gchar *log = (gchar *) g_malloc0(LOG_SIZE * sizeof (char));
-        UFO_RESOURCES_CHECK_CLERR (clGetProgramBuildInfo (program,
-                                                   priv->opencl_devices[0][0],
-                                                   CL_PROGRAM_BUILD_LOG,
-                                                   LOG_SIZE, (void *) log, NULL));
-        g_print ("\n=== Build log for s===%s\n\n", log);
-        g_free (log);
+        handle_build_error (program, priv->devices[0], errcode, error);
         return NULL;
     }
 
+    priv->programs = g_list_append (priv->programs, program);
+
+    g_free (build_options);
     return program;
 }
 
-static cl_program
-resources_add_program (UfoResources *resources, const gchar *filename, const gchar *options, GError **error)
+static gchar *
+get_first_kernel_name (const gchar *source)
 {
-    g_return_val_if_fail (UFO_IS_RESOURCES (resources) || (filename != NULL), FALSE);
-    UfoResourcesPrivate *priv = resources->priv;
-
-    /* Programs might be added multiple times if this is not locked */
-    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
-    g_static_mutex_lock (&mutex);
-
-    /* Don't process the kernel file again, if already load */
-    cl_program program = g_hash_table_lookup (priv->opencl_programs, filename);
+    GRegex *regex;
+    gchar *name = NULL;
+    GMatchInfo *match = NULL;
+    GError *error = NULL;
 
-    if (program != NULL) {
-        g_static_mutex_unlock (&mutex);
-        return program;
-    }
-
-    gchar *path = resources_find_path (priv, filename);
+    regex = g_regex_new ("__kernel\\svoid\\s([A-Za-z][A-Za-z0-9_]+)",
+                         G_REGEX_MULTILINE, 0, &error);
 
-    if (path == NULL) {
-        g_set_error (error,
-                     UFO_RESOURCES_ERROR,
-                     UFO_RESOURCES_ERROR_LOAD_PROGRAM,
-                     "Could not find `%s'. Maybe you forgot to pass a configuration?", filename);
-        g_static_mutex_unlock (&mutex);
+    if (error != NULL) {
+        g_error ("%s", error->message);
+        g_error_free (error);
         return NULL;
     }
 
-    gchar *buffer = resources_load_opencl_program (path);
-    g_free (path);
+    if (g_regex_match (regex, source, 0, &match))
+        name = g_match_info_fetch (match, 1);
 
-    if (buffer == NULL) {
-        g_set_error (error,
-                     UFO_RESOURCES_ERROR,
-                     UFO_RESOURCES_ERROR_LOAD_PROGRAM,
-                     "Could not open `%s'", filename);
-        g_static_mutex_unlock (&mutex);
-        return NULL;
-    }
-
-    program = add_program_from_source (priv, buffer, options, error);
-    g_message ("Added program %p from `%s`", (gpointer) program, filename);
-
-    if (program != NULL)
-        g_hash_table_insert (priv->opencl_programs, g_strdup (filename), program);
-
-    g_static_mutex_unlock (&mutex);
-    g_free (buffer);
-    return program;
+    g_match_info_free (match);
+    g_regex_unref (regex);
+    return name;
 }
 
 static cl_kernel
-resources_get_kernel (UfoResourcesPrivate *priv,
-                      cl_program program,
-                      const gchar *kernel_name,
-                      GError **error)
+create_kernel (UfoResourcesPrivate *priv,
+               cl_program program,
+               const gchar *kernel_name,
+               GError **error)
 {
+    cl_kernel kernel;
+    gchar *name;
     cl_int errcode = CL_SUCCESS;
-    cl_kernel kernel = clCreateKernel (program, kernel_name, &errcode);
+
+    if (kernel_name == NULL) {
+        gchar *source;
+        gsize size;
+
+        UFO_RESOURCES_CHECK_CLERR (clGetProgramInfo (program, CL_PROGRAM_SOURCE, 0, NULL, &size));
+        source = g_malloc0 (size);
+        UFO_RESOURCES_CHECK_CLERR (clGetProgramInfo (program, CL_PROGRAM_SOURCE, size, source, NULL));
+        name = get_first_kernel_name (source);
+        g_free (source);
+    }
+    else {
+        name = g_strdup (kernel_name);
+    }
+
+    kernel = clCreateKernel (program, name, &errcode);
+    g_free (name);
 
     if (kernel == NULL || errcode != CL_SUCCESS) {
         g_set_error (error,
@@ -375,56 +605,130 @@ resources_get_kernel (UfoResourcesPrivate *priv,
         return NULL;
     }
 
-    priv->opencl_kernels = g_list_append (priv->opencl_kernels, kernel);
+    priv->kernels = g_list_append (priv->kernels, kernel);
     return kernel;
 }
 
+static gchar *
+create_cache_key (const gchar *filename,
+                  const gchar *kernelname)
+{
+    return g_strdup_printf ("%s:%s", filename, kernelname);
+}
+
 /**
  * ufo_resources_get_kernel:
  * @resources: A #UfoResources object
  * @filename: Name of the .cl kernel file
- * @kernel: Name of a kernel
- * @error: Return location for a GError from #UfoResourcesError, or NULL
+ * @kernel: Name of a kernel, or %NULL
+ * @error: Return location for a GError from #UfoResourcesError, or %NULL
  *
  * Loads a and builds a kernel from a file. The file is searched in the current
- * working directory and all paths added through
- * ufo_resources_add_paths ().
+ * working directory and all paths added through ufo_resources_add_paths (). If
+ * @kernel is %NULL, the first encountered kernel is returned.
  *
  * Returns: (transfer none): a cl_kernel object that is load from @filename or %NULL on error
  */
 gpointer
 ufo_resources_get_kernel (UfoResources *resources,
                           const gchar *filename,
-                          const gchar *kernel,
+                          const gchar *kernelname,
                           GError **error)
 {
     UfoResourcesPrivate *priv;
+    gchar *path;
+    gchar *buffer;
     cl_program program;
-    GError *tmp_error = NULL;
 
     g_return_val_if_fail (UFO_IS_RESOURCES (resources) &&
-                          (filename != NULL) &&
-                          (kernel != NULL), NULL);
+                          (filename != NULL), NULL);
 
     priv = resources->priv;
-    program = resources_add_program (resources, filename, "", &tmp_error);
+    path = lookup_kernel_path (priv, filename);
+
+    if (path == NULL) {
+        g_set_error (error, UFO_RESOURCES_ERROR, UFO_RESOURCES_ERROR_LOAD_PROGRAM,
+                     "Could not find `%s'. Maybe you forgot to pass a configuration?", filename);
+        return NULL;
+    }
+
+    buffer = read_file (path);
+    g_free (path);
 
-    if (program == NULL) {
-        g_propagate_error (error, tmp_error);
+    if (buffer == NULL) {
+        g_set_error (error, UFO_RESOURCES_ERROR, UFO_RESOURCES_ERROR_LOAD_PROGRAM,
+                     "Could not open `%s'", filename);
         return NULL;
     }
 
-    return resources_get_kernel (priv, program, kernel, error);
+    program = add_program_from_source (priv, buffer, "", error);
+    g_debug ("Added program %p from `%s`", (gpointer) program, filename);
+    g_free (buffer);
+
+    return create_kernel (priv, program, kernelname, error);
+}
+
+/**
+ * ufo_resources_get_cached_kernel:
+ * @resources: A #UfoResources object
+ * @filename: Name of the .cl kernel file
+ * @kernel: Name of a kernel, or %NULL
+ * @error: Return location for a GError from #UfoResourcesError, or %NULL
+ *
+ * Loads a and builds a kernel from a file. The file is searched in the current
+ * working directory and all paths added through ufo_resources_add_paths (). If
+ * @kernel is %NULL, the first encountered kernel is returned. The kernel object
+ * is cached and should not be used by two threads concurrently.
+ *
+ * Returns: (transfer none): a cl_kernel object that is load from @filename or %NULL on error
+ */
+gpointer
+ufo_resources_get_cached_kernel (UfoResources *resources,
+                                 const gchar *filename,
+                                 const gchar *kernelname,
+                                 GError **error)
+{
+    UfoResourcesPrivate *priv;
+    cl_kernel kernel;
+
+    g_return_val_if_fail (UFO_IS_RESOURCES (resources) &&
+                          (filename != NULL), NULL);
+
+    priv = resources->priv;
+
+    if (kernelname != NULL) {
+        gchar *cache_key;
+
+        cache_key = create_cache_key (filename, kernelname);
+        kernel = g_hash_table_lookup (priv->kernel_cache, cache_key);
+
+        if (kernel != NULL) {
+            g_free (cache_key);
+            return kernel;
+        }
+    }
+
+    kernel = ufo_resources_get_kernel (resources, filename, kernelname, error);
+
+    if (kernel != NULL && kernelname != NULL) {
+        gchar *cache_key;
+
+        cache_key = create_cache_key (filename, kernelname);
+        g_hash_table_insert (priv->kernel_cache, cache_key, kernel);
+    }
+
+    return kernel;
 }
 
 /**
  * ufo_resources_get_kernel_from_source:
  * @resources: A #UfoResources
  * @source: OpenCL source string
- * @kernel: Name of a kernel
+ * @kernel: Name of a kernel or %NULL
  * @error: Return location for a GError from #UfoResourcesError, or NULL
  *
- * Loads and builds a kernel from a string.
+ * Loads and builds a kernel from a string. If @kernel is %NULL, the first
+ * kernel defined in @source is used.
  *
  * Returns: (transfer none): a cl_kernel object that is load from @filename
  */
@@ -438,23 +742,12 @@ ufo_resources_get_kernel_from_source (UfoResources *resources,
     cl_program program;
 
     g_return_val_if_fail (UFO_IS_RESOURCES (resources) &&
-                          (source != NULL) &&
-                          (kernel != NULL), NULL);
+                          (source != NULL), NULL);
 
     priv = UFO_RESOURCES_GET_PRIVATE (resources);
     program = add_program_from_source (priv, source, NULL, error);
-
-    /*
-     * We add the program under a fake file name. This looks very brittle to me
-     * (kernel name could be the same as a source filename) but it should work
-     * in most cases.
-     */
-    if (program != NULL)
-        g_hash_table_insert (priv->opencl_programs, g_strdup (kernel), program);
-    else
-        return NULL;
-
-    return resources_get_kernel (priv, program, kernel, error);
+    g_debug ("Added program %p from source", (gpointer) program);
+    return create_kernel (priv, program, kernel, error);
 }
 
 /**
@@ -470,16 +763,16 @@ gpointer
 ufo_resources_get_context (UfoResources *resources)
 {
     g_return_val_if_fail (UFO_IS_RESOURCES (resources), NULL);
-    return resources->priv->opencl_context;
+    return resources->priv->context;
 }
 
 /**
- * ufo_resources_get_cmd_queues:
+ * ufo_resources_get_cmd_queues: (skip)
  * @resources: A #UfoResources
  *
  * Get all command queues managed by @resources.
  *
- * Return value: (element-type gpointer) (transfer container): List with
+ * Returns: (transfer container) (element-type gpointer): List with
  * cl_command_queue objects. Free with g_list_free() but not its elements.
  */
 GList *
@@ -491,12 +784,72 @@ ufo_resources_get_cmd_queues (UfoResources *resources)
     g_return_val_if_fail (UFO_IS_RESOURCES (resources), NULL);
     priv = resources->priv;
 
-    for (guint i = 0; i < priv->num_devices[0]; i++)
+    for (guint i = 0; i < priv->n_devices; i++)
         result = g_list_append (result, priv->command_queues[i]);
 
     return result;
 }
 
+/**
+ * ufo_resources_get_devices: (skip)
+ * @resources: A #UfoResources
+ *
+ * Get all devices queues managed by @resources.
+ *
+ * Returns: (element-type gpointer) (transfer container): List with
+ * cl_device_id objects. Free with g_list_free() but not its elements.
+ */
+GList *
+ufo_resources_get_devices (UfoResources *resources)
+{
+    UfoResourcesPrivate *priv;
+    GList *result = NULL;
+
+    g_return_val_if_fail (UFO_IS_RESOURCES(resources), NULL);
+    priv = resources->priv;
+
+    for (guint i = 0; i < priv->n_devices; i++)
+        result = g_list_append (result, priv->devices[i]);
+
+    return result;
+}
+
+/**
+ * ufo_resources_get_mapped_cmd_queues:
+ * @resources: A #UfoResources
+ *
+ * Get all devices queues managed by @resources.
+ *
+ * Return value: (transfer container): Hash table with cl_device_id objects as key and
+ * cl_command_queue objects as value. Free with g_hash_table_destroy() but not
+ * its elements.
+ */
+GHashTable *
+ufo_resources_get_mapped_cmd_queues (UfoResources *resources)
+{
+    UfoResourcesPrivate *priv;
+    GHashTable *result = NULL;
+
+    g_return_val_if_fail (UFO_IS_RESOURCES (resources), NULL);
+    priv = resources->priv;
+    result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                    NULL, (GDestroyNotify) release_program);
+
+    for (guint i = 0; i < priv->n_devices; i++)
+        g_hash_table_insert (result, priv->devices[i], priv->command_queues[i]);
+
+    return result;
+}
+
+static GList *
+append_config_paths (GList *list, UfoConfig *config)
+{
+    GList *paths;
+
+    paths = ufo_config_get_paths (config);
+    return g_list_concat (list, paths);
+}
+
 static void
 ufo_resources_set_property (GObject *object,
                             guint property_id,
@@ -515,12 +868,11 @@ ufo_resources_set_property (GObject *object,
 
                 if (value_object != NULL) {
                     UfoConfig *config;
-                    GList *paths;
 
                     config = UFO_CONFIG (value_object);
-                    paths = ufo_config_get_paths (config);
-                    priv->kernel_paths = g_list_concat (priv->kernel_paths, paths);
-                    g_list_foreach (paths, (GFunc) append_include_path, priv->include_paths);
+                    priv->kernel_paths = append_config_paths (priv->kernel_paths, config);
+                    priv->include_paths = append_config_paths (priv->include_paths, config);
+
                     g_object_ref (config);
                     priv->config = config;
                 }
@@ -562,45 +914,83 @@ ufo_resources_dispose (GObject *object)
         priv->config = NULL;
     }
 
-    G_OBJECT_CLASS (ufo_resources_parent_class)->finalize (object);
+    G_OBJECT_CLASS (ufo_resources_parent_class)->dispose (object);
+}
+
+static void
+list_free_full (GList **list,
+                GFunc free_func)
+{
+    g_assert (list != NULL);
+    g_list_foreach (*list, free_func, NULL);
+    g_list_free (*list);
+    *list = NULL;
 }
 
 static void
 ufo_resources_finalize (GObject *object)
 {
-    UfoResourcesPrivate *priv = UFO_RESOURCES_GET_PRIVATE (object);
+    UfoResourcesPrivate *priv;
 
-    g_hash_table_destroy (priv->opencl_programs);
-    g_list_foreach (priv->kernel_paths, (GFunc) g_free, NULL);
-    g_list_free (priv->kernel_paths);
-    g_list_foreach (priv->opencl_kernels, (GFunc) resources_release_kernel, NULL);
-    g_list_free (priv->opencl_kernels);
+    priv = UFO_RESOURCES_GET_PRIVATE (object);
 
-    for (guint i = 0; i < priv->num_devices[0]; i++)
-        UFO_RESOURCES_CHECK_CLERR (clReleaseCommandQueue (priv->command_queues[i]));
+    g_clear_error (&priv->construct_error);
+    g_hash_table_destroy (priv->kernel_cache);
+
+    list_free_full (&priv->kernel_paths, (GFunc) g_free);
+    list_free_full (&priv->include_paths, (GFunc) g_free);
+    list_free_full (&priv->kernels, (GFunc) release_kernel);
+    list_free_full (&priv->programs, (GFunc) release_program);
 
-    UFO_RESOURCES_CHECK_CLERR (clReleaseContext (priv->opencl_context));
+    for (guint i = 0; i < priv->n_devices; i++)
+        UFO_RESOURCES_CHECK_CLERR (clReleaseCommandQueue (priv->command_queues[i]));
 
-    g_string_free (priv->opencl_build_options, TRUE);
-    g_string_free (priv->include_paths, TRUE);
+    if (priv->context)
+        UFO_RESOURCES_CHECK_CLERR (clReleaseContext (priv->context));
 
-    for (guint i = 0; i < priv->num_platforms; i ++)
-        g_free (priv->opencl_devices[i]);
+    g_string_free (priv->build_opts, TRUE);
 
-    g_free (priv->num_devices);
-    g_free (priv->opencl_devices);
-    g_free (priv->opencl_platforms);
+    g_free (priv->devices);
     g_free (priv->command_queues);
 
-    priv->num_devices = NULL;
-    priv->opencl_kernels = NULL;
-    priv->opencl_devices = NULL;
-    priv->opencl_platforms = NULL;
+    priv->kernels = NULL;
+    priv->devices = NULL;
 
     G_OBJECT_CLASS (ufo_resources_parent_class)->finalize (object);
     g_debug ("UfoResources: finalized");
 }
 
+static gboolean
+ufo_resources_initable_init (GInitable *initable,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+    UfoResources *resources;
+    UfoResourcesPrivate *priv;
+
+    g_return_val_if_fail (UFO_IS_RESOURCES (initable), FALSE);
+
+    if (cancellable != NULL) {
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                             "Cancellable initialization not supported");
+        return FALSE;
+    }
+
+    resources = UFO_RESOURCES (initable);
+    priv = resources->priv;
+
+    if (!initialize_opencl (priv, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+static void
+ufo_resources_initable_iface_init (GInitableIface *iface)
+{
+    iface->init = ufo_resources_initable_init;
+}
+
 static void
 ufo_resources_class_init (UfoResourcesClass *klass)
 {
@@ -620,72 +1010,18 @@ static void
 ufo_resources_init (UfoResources *self)
 {
     UfoResourcesPrivate *priv;
+
     self->priv = priv = UFO_RESOURCES_GET_PRIVATE (self);
 
     priv->config = NULL;
-    priv->opencl_programs = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                   g_free, (GDestroyNotify) resources_release_program);
-    priv->opencl_kernels = NULL;
-    priv->opencl_platforms = NULL;
-    priv->opencl_build_options = g_string_new ("-cl-mad-enable ");
-    priv->include_paths = g_string_new ("-I. ");
+    priv->programs = NULL;
+    priv->kernels = NULL;
+    priv->kernel_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+    priv->build_opts = g_string_new ("-cl-mad-enable ");
+    priv->include_paths = g_list_append (NULL, g_strdup ("."));
 
     priv->kernel_paths = g_list_append (NULL, g_strdup ("."));
     priv->kernel_paths = g_list_append (priv->kernel_paths, g_strdup (UFO_PLUGIN_DIR));
-
-    /* initialize OpenCL subsystem */
-    int errcode = CL_SUCCESS;
-    UFO_RESOURCES_CHECK_CLERR (clGetPlatformIDs (0, NULL, &priv->num_platforms));
-    priv->opencl_platforms = g_malloc0(priv->num_platforms * sizeof (cl_platform_id));
-
-    UFO_RESOURCES_CHECK_CLERR (clGetPlatformIDs (priv->num_platforms, priv->opencl_platforms, NULL));
-    priv->num_devices = g_malloc0(priv->num_platforms * sizeof (cl_uint));
-    priv->opencl_devices = g_malloc0(priv->num_platforms * sizeof (cl_device_id *));
-
-    /* Get devices for each available platform */
-    gchar *info_buffer = g_malloc0 (256);
-
-    for (guint i = 0; i < priv->num_platforms; i++) {
-        cl_uint num_devices;
-        cl_platform_id platform = priv->opencl_platforms[i];
-
-        UFO_RESOURCES_CHECK_CLERR (clGetPlatformInfo (platform, CL_PLATFORM_VENDOR, 256, info_buffer, NULL));
-
-        if (g_str_has_prefix (info_buffer, "NVIDIA"))
-            g_string_append (priv->opencl_build_options, "-cl-nv-verbose -DVENDOR=NVIDIA");
-        else if (g_str_has_prefix (info_buffer, "Advanced Micro Devices"))
-            g_string_append (priv->opencl_build_options, "-DVENDOR=AMD");
-
-        UFO_RESOURCES_CHECK_CLERR (clGetDeviceIDs (platform,
-                                                   CL_DEVICE_TYPE_ALL,
-                                                   0, NULL,
-                                                   &num_devices));
-        priv->opencl_devices[i] = g_malloc0 (num_devices * sizeof (cl_device_id));
-
-        UFO_RESOURCES_CHECK_CLERR (clGetDeviceIDs (platform,
-                                                   CL_DEVICE_TYPE_ALL,
-                                                   num_devices, priv->opencl_devices[i],
-                                                   NULL));
-        priv->num_devices[i] = num_devices;
-    }
-
-    g_free (info_buffer);
-    cl_command_queue_properties queue_properties = CL_QUEUE_PROFILING_ENABLE;
-
-    /* XXX: create context for each platform?! */
-    if (priv->num_platforms > 0) {
-        priv->opencl_context = clCreateContext (NULL,
-                                                priv->num_devices[0],
-                                                priv->opencl_devices[0],
-                                                NULL, NULL, &errcode);
-        UFO_RESOURCES_CHECK_CLERR (errcode);
-        priv->command_queues = g_malloc0 (priv->num_devices[0] * sizeof (cl_command_queue));
-
-        for (guint i = 0; i < priv->num_devices[0]; i++) {
-            priv->command_queues[i] = clCreateCommandQueue (priv->opencl_context,
-                                      priv->opencl_devices[0][i],
-                                      queue_properties, &errcode);
-            UFO_RESOURCES_CHECK_CLERR (errcode);
-        }
-    }
+    priv->kernel_paths = g_list_append (priv->kernel_paths, g_strdup ("/usr/lib/ufo"));
+    priv->kernel_paths = g_list_append (priv->kernel_paths, g_strdup ("/usr/local/lib/ufo"));
 }
diff --git a/ufo/ufo-resources.h b/ufo/ufo-resources.h
index af465c8..2148939 100644
--- a/ufo/ufo-resources.h
+++ b/ufo/ufo-resources.h
@@ -44,6 +44,7 @@ typedef struct _UfoResourcesPrivate    UfoResourcesPrivate;
 
 
 typedef enum {
+    UFO_RESOURCES_ERROR_GENERAL,
     UFO_RESOURCES_ERROR_LOAD_PROGRAM,
     UFO_RESOURCES_ERROR_CREATE_PROGRAM,
     UFO_RESOURCES_ERROR_BUILD_PROGRAM,
@@ -61,6 +62,20 @@ typedef enum {
     if ((error) != CL_SUCCESS) g_log("ocl", G_LOG_LEVEL_CRITICAL, "Error <%s:%i>: %s", __FILE__, __LINE__, ufo_resources_clerr((error))); }
 
 /**
+ * UFO_RESOURCES_CHECK_AND_SET:
+ * @error: OpenCL error code
+ * @g_error_loc: Return location for a GError or %NULL
+ *
+ * Check @error and set @g_error_loc accordingly.
+ *
+ * Returns: %TRUE if no error occurred, %FALSE otherwise.
+ */
+#define UFO_RESOURCES_CHECK_AND_SET(error, g_error_loc) { \
+    if ((error) != CL_SUCCESS) \
+        g_set_error (g_error_loc, UFO_RESOURCES_ERROR, UFO_RESOURCES_ERROR_GENERAL, \
+                     "OpenCL Error: %s", ufo_resources_clerr((error))); }
+
+/**
  * UfoResources:
  *
  * Manages OpenCL resources. The contents of the #UfoResources structure
@@ -83,17 +98,24 @@ struct _UfoResourcesClass {
     GObjectClass parent_class;
 };
 
-UfoResources   * ufo_resources_new                      (UfoConfig      *config);
+UfoResources   * ufo_resources_new                      (UfoConfig      *config,
+                                                         GError        **error);
 gpointer         ufo_resources_get_kernel               (UfoResources   *resources,
                                                          const gchar    *filename,
                                                          const gchar    *kernel,
                                                          GError        **error);
+gpointer         ufo_resources_get_cached_kernel        (UfoResources   *resources,
+                                                         const gchar    *filename,
+                                                         const gchar    *kernel,
+                                                         GError        **error);
 gpointer         ufo_resources_get_kernel_from_source   (UfoResources   *resources,
                                                          const gchar    *source,
                                                          const gchar    *kernel,
                                                          GError        **error);
 gpointer         ufo_resources_get_context              (UfoResources   *resources);
 GList          * ufo_resources_get_cmd_queues           (UfoResources   *resources);
+GList          * ufo_resources_get_devices              (UfoResources   *resources);
+GHashTable     * ufo_resources_get_mapped_cmd_queues    (UfoResources   *resources);
 const gchar    * ufo_resources_clerr                    (int             error);
 GType            ufo_resources_get_type                 (void);
 GQuark           ufo_resources_error_quark              (void);
diff --git a/ufo/ufo-scheduler.c b/ufo/ufo-scheduler.c
index 4b39906..f7ca218 100644
--- a/ufo/ufo-scheduler.c
+++ b/ufo/ufo-scheduler.c
@@ -16,15 +16,21 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
  */
+#include "config.h"
 
 #ifdef __APPLE__
 #include <OpenCL/cl.h>
 #else
 #include <CL/cl.h>
 #endif
+#include <gio/gio.h>
 #include <stdio.h>
 #include <string.h>
 
+#ifdef HAVE_PYTHON
+#include <Python.h>
+#endif
+
 #include <ufo/ufo-buffer.h>
 #include <ufo/ufo-config.h>
 #include <ufo/ufo-configurable.h>
@@ -46,8 +52,12 @@
  * on CPU and GPU hardware.
  */
 
+static void ufo_scheduler_initable_iface_init (GInitableIface *iface);
+
 G_DEFINE_TYPE_WITH_CODE (UfoScheduler, ufo_scheduler, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (UFO_TYPE_CONFIGURABLE, NULL))
+                         G_IMPLEMENT_INTERFACE (UFO_TYPE_CONFIGURABLE, NULL)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                ufo_scheduler_initable_iface_init))
 
 #define UFO_SCHEDULER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_SCHEDULER, UfoSchedulerPrivate))
 
@@ -60,16 +70,20 @@ typedef struct {
 } TaskLocalData;
 
 struct _UfoSchedulerPrivate {
+    GError          *construct_error;
     UfoConfig       *config;
     UfoResources    *resources;
     GList           *remotes;
+    UfoRemoteMode    mode;
     gboolean         expand;
+    gboolean         trace;
 };
 
 enum {
     PROP_0,
     PROP_EXPAND,
     PROP_REMOTES,
+    PROP_ENABLE_TRACING,
     N_PROPERTIES,
 
     /* Here come the overriden properties that we don't install ourselves. */
@@ -79,6 +93,17 @@ enum {
 static GParamSpec *properties[N_PROPERTIES] = { NULL, };
 
 /**
+ * UfoSchedulerError:
+ * @UFO_SCHEDULER_ERROR_SETUP: Could not start scheduler due to error
+ */
+GQuark
+ufo_scheduler_error_quark (void)
+{
+    return g_quark_from_static_string ("ufo-scheduler-error-quark");
+}
+
+
+/**
  * ufo_scheduler_new:
  * @config: A #UfoConfig or %NULL
  * @remotes: (element-type utf8): A #GList with strings describing remote machines or %NULL
@@ -125,8 +150,8 @@ ufo_scheduler_get_context (UfoScheduler *scheduler)
  * @scheduler: A #UfoScheduler
  * @split: %TRUE if task graph should be split
  *
- * Sets whether the task graph should be expanded before execution to increase
- * multi GPU performance.
+ * Sets whether the task graph should be expanded to accomodate for a multi GPU
+ * system. Each suitable branch will be run on another GPU.
  */
 void
 ufo_scheduler_set_task_expansion (UfoScheduler *scheduler,
@@ -136,6 +161,23 @@ ufo_scheduler_set_task_expansion (UfoScheduler *scheduler,
     g_object_set (G_OBJECT (scheduler), "expand", expand, NULL);
 }
 
+/**
+ * ufo_scheduler_set_remote_mode:
+ * @scheduler: A #UfoScheduler
+ * @mode: Mode of remote execution.
+ *
+ * Sets the mode of remote execution.
+ *
+ * See: #UfoRemoteMode.
+ */
+void
+ufo_scheduler_set_remote_mode (UfoScheduler *scheduler,
+                               UfoRemoteMode mode)
+{
+    g_return_if_fail (UFO_IS_SCHEDULER (scheduler));
+    scheduler->priv->mode = mode;
+}
+
 static gboolean
 get_inputs (TaskLocalData *tld,
             UfoBuffer **inputs)
@@ -181,77 +223,146 @@ release_inputs (TaskLocalData *tld,
     }
 }
 
-static void
-exchange_data (UfoBuffer *input,
-               TaskLocalData *tld)
+static gboolean
+any (gboolean *values,
+     guint n_values)
 {
-    UfoRemoteNode *remote;
-    UfoGroup *group;
-    UfoBuffer *output;
-    UfoRequisition requisition;
+    gboolean result = FALSE;
 
-    remote = UFO_REMOTE_NODE (ufo_task_node_get_proc_node (UFO_TASK_NODE (tld->task)));
-    ufo_remote_node_send_inputs (remote, &input);
-    release_inputs (tld, &input);
+    for (guint i = 0; i < n_values; i++)
+        result = result || values[i];
 
-    ufo_remote_node_get_requisition (remote, &requisition);
-    group = ufo_task_node_get_out_group (UFO_TASK_NODE (tld->task));
-    output = ufo_group_pop_output_buffer (group, &requisition);
-    ufo_remote_node_get_result (remote, output);
-    ufo_group_push_output_buffer (group, output);
+    return result;
 }
 
 static void
 run_remote_task (TaskLocalData *tld)
 {
     UfoRemoteNode *remote;
-    UfoBuffer *input;
     guint n_remote_gpus;
-    GThreadPool *pool;
-    GError *error = NULL;
+    gboolean *alive;
+    gboolean active = TRUE;
 
     g_assert (tld->n_inputs == 1);
+
     remote = UFO_REMOTE_NODE (ufo_task_node_get_proc_node (UFO_TASK_NODE (tld->task)));
     n_remote_gpus = ufo_remote_node_get_num_gpus (remote);
-    pool = g_thread_pool_new ((GFunc) exchange_data, tld, (gint) n_remote_gpus, TRUE, &error);
-    g_assert_no_error (error);
+    alive = g_new0 (gboolean, n_remote_gpus);
 
     /*
      * We launch a new thread for each incoming input data set because then we
      * can send as many items as we have remote GPUs available without waiting
      * for processing to stop.
      */
-    while (1) {
-        if (get_inputs (tld, &input))
-            g_thread_pool_push (pool, input, &error);
-        else
-            break;
+    while (active) {
+        for (guint i = 0; i < n_remote_gpus; i++) {
+            UfoBuffer *input;
+
+            if (get_inputs (tld, &input)) {
+                ufo_remote_node_send_inputs (remote, &input);
+                release_inputs (tld, &input);
+                alive[i] = TRUE;
+            }
+            else {
+                alive[i] = FALSE;
+            }
+        }
+
+        for (guint i = 0; i < n_remote_gpus; i++) {
+            UfoGroup *group;
+            UfoBuffer *output;
+            UfoRequisition requisition;
+
+            if (!alive[i])
+                continue;
+
+            ufo_remote_node_get_requisition (remote, &requisition);
+            group = ufo_task_node_get_out_group (UFO_TASK_NODE (tld->task));
+            output = ufo_group_pop_output_buffer (group, &requisition);
+            ufo_remote_node_get_result (remote, output);
+            ufo_group_push_output_buffer (group, output);
+        }
+
+        active = any (alive, n_remote_gpus);
     }
 
-    g_thread_pool_free (pool, FALSE, TRUE);
+    g_free (alive);
     ufo_group_finish (ufo_task_node_get_out_group (UFO_TASK_NODE (tld->task)));
 }
 
+static gboolean
+check_implementation (UfoTaskNode *node,
+                      const gchar *func,
+                      const gchar *type,
+                      gboolean assertion)
+{
+    if (!assertion) {
+        g_error ("%s is not implemented, although %s is a %s",
+                 func, ufo_task_node_get_plugin_name (node), type);
+    }
+
+    return assertion;
+}
+
+static gboolean
+is_correctly_implemented (UfoTaskNode *node,
+                          UfoTaskMode mode,
+                          UfoTaskProcessFunc process,
+                          UfoTaskGenerateFunc generate)
+{
+    switch (mode) {
+        case UFO_TASK_MODE_GENERATOR:
+            return check_implementation (node, "generate", "generator", generate != NULL);
+
+        case UFO_TASK_MODE_PROCESSOR:
+            return check_implementation (node, "process", "processor", process != NULL);
+
+        case UFO_TASK_MODE_REDUCTOR:
+            return check_implementation (node, "process or generate", "reductor",
+                                         (process != NULL) && (generate != NULL));
+        default:
+            g_error ("Unknown mode");
+            return FALSE;
+    }
+}
+
 static gpointer
 run_task (TaskLocalData *tld)
 {
     UfoBuffer *inputs[tld->n_inputs];
     UfoBuffer *output;
     UfoTaskNode *node;
+    UfoProfiler *profiler;
+    UfoTaskProcessFunc process;
+    UfoTaskGenerateFunc generate;
     UfoRequisition requisition;
     gboolean active;
 
     node = UFO_TASK_NODE (tld->task);
     active = TRUE;
     output = NULL;
+    profiler = g_object_ref (ufo_task_node_get_profiler (node));
 
     if (UFO_IS_REMOTE_TASK (tld->task)) {
         run_remote_task (tld);
         return NULL;
     }
 
+    if (UFO_IS_GPU_TASK (tld->task)) {
+        process = (UfoTaskProcessFunc) ufo_gpu_task_process;
+        generate = (UfoTaskGenerateFunc) ufo_gpu_task_generate;
+    }
+    else {
+        process = (UfoTaskProcessFunc) ufo_cpu_task_process;
+        generate = (UfoTaskGenerateFunc) ufo_cpu_task_generate;
+    }
+
+    if (!is_correctly_implemented (node, tld->mode, process, generate))
+        return NULL;
+
     while (active) {
         UfoGroup *group;
+        gboolean produces;
 
         group = ufo_task_node_get_out_group (node);
 
@@ -265,122 +376,74 @@ run_task (TaskLocalData *tld)
 
         /* Get output buffers */
         ufo_task_get_requisition (tld->task, inputs, &requisition);
+        produces = requisition.n_dims > 0;
 
-        if (requisition.n_dims > 0) {
+        if (produces) {
             output = ufo_group_pop_output_buffer (group, &requisition);
             g_assert (output != NULL);
         }
 
-        /* Process */
-        if (UFO_IS_GPU_TASK (tld->task)) {
-            UfoGpuNode *gpu_node;
-
-            if (output != NULL)
-                ufo_buffer_discard_location (output, UFO_LOCATION_HOST);
-
-            gpu_node = UFO_GPU_NODE (ufo_task_node_get_proc_node (node));
-
-            switch (tld->mode) {
-                case UFO_TASK_MODE_SINGLE:
-                    active = ufo_gpu_task_process (UFO_GPU_TASK (tld->task),
-                                                   inputs, output,
-                                                   &requisition, gpu_node);
-                    break;
-
-                case UFO_TASK_MODE_GENERATE:
-                case UFO_TASK_MODE_REDUCE:
-                    do {
-                        ufo_gpu_task_process (UFO_GPU_TASK (tld->task),
-                                              inputs, output,
-                                              &requisition, gpu_node);
-                        release_inputs (tld, inputs);
-                        active = get_inputs (tld, inputs);
-                    } while (active);
-                    break;
-            }
-
-            if (tld->mode == UFO_TASK_MODE_REDUCE)
-                ufo_gpu_task_reduce (UFO_GPU_TASK (tld->task),
-                                     output,
-                                     &requisition,
-                                     gpu_node);
+        if (output != NULL)
+            ufo_buffer_discard_location (output);
+
+        switch (tld->mode) {
+            case UFO_TASK_MODE_PROCESSOR:
+                ufo_profiler_trace_event (profiler, "process", "B");
+                active = process (tld->task, inputs, output, &requisition);
+                ufo_profiler_trace_event (profiler, "process", "E");
+                break;
+
+            case UFO_TASK_MODE_REDUCTOR:
+                do {
+                    ufo_profiler_trace_event (profiler, "process", "B");
+                    process (tld->task, inputs, output, &requisition);
+                    ufo_profiler_trace_event (profiler, "process", "E");
+
+                    release_inputs (tld, inputs);
+                    active = get_inputs (tld, inputs);
+                } while (active);
+                break;
+
+            case UFO_TASK_MODE_GENERATOR:
+                ufo_profiler_trace_event (profiler, "generate", "B");
+                active = generate (tld->task, output, &requisition);
+                ufo_profiler_trace_event (profiler, "generate", "E");
+                break;
         }
-        else if (UFO_IS_CPU_TASK (tld->task)) {
-            if (output != NULL)
-                ufo_buffer_discard_location (output, UFO_LOCATION_DEVICE);
-
-            switch (tld->mode) {
-                case UFO_TASK_MODE_SINGLE:
-                    active = ufo_cpu_task_process (UFO_CPU_TASK (tld->task), inputs, output, &requisition);
-                    break;
-
-                case UFO_TASK_MODE_GENERATE:
-                case UFO_TASK_MODE_REDUCE:
-                    do {
-                        ufo_cpu_task_process (UFO_CPU_TASK (tld->task),
-                                              inputs,
-                                              output,
-                                              &requisition);
-                        release_inputs (tld, inputs);
-                        active = get_inputs (tld, inputs);
-                    } while (active);
-                    break;
-            }
 
-            if (tld->mode == UFO_TASK_MODE_REDUCE)
-                ufo_cpu_task_reduce (UFO_CPU_TASK (tld->task), output, &requisition);
-        }
+        if (active && produces && (tld->mode != UFO_TASK_MODE_REDUCTOR))
+            ufo_group_push_output_buffer (group, output);
 
         /* Release buffers for further consumption */
-        release_inputs (tld, inputs);
-
-        if (requisition.n_dims > 0) {
-            switch (tld->mode) {
-                case UFO_TASK_MODE_SINGLE:
-                    if (active)
-                        ufo_group_push_output_buffer (group, output);
-                    else
-                        ufo_group_finish (group);
-                    break;
-
-                case UFO_TASK_MODE_REDUCE:
+        if (active)
+            release_inputs (tld, inputs);
+
+        if (tld->mode == UFO_TASK_MODE_REDUCTOR) {
+            active = TRUE;
+
+            do {
+                ufo_profiler_trace_event (profiler, "generate", "B");
+                active = generate (tld->task, output, &requisition);
+                ufo_profiler_trace_event (profiler, "generate", "E");
+
+                if (active) {
                     ufo_group_push_output_buffer (group, output);
-                    ufo_group_finish (group);
-                    break;
-
-                case UFO_TASK_MODE_GENERATE:
-                    {
-                        do {
-                            if (UFO_IS_GPU_TASK (tld->task)) {
-                                UfoGpuNode *gpu_node;
-
-                                ufo_buffer_discard_location (output, UFO_LOCATION_HOST);
-                                gpu_node = UFO_GPU_NODE (ufo_task_node_get_proc_node (node));
-                                active = ufo_gpu_task_generate (UFO_GPU_TASK (tld->task),
-                                                                output,
-                                                                &requisition, gpu_node);
-                            }
-                            else {
-                                active = ufo_cpu_task_generate (UFO_CPU_TASK (tld->task),
-                                                                output,
-                                                                &requisition);
-                            }
-
-                            if (active) {
-                                ufo_group_push_output_buffer (group, output);
-                                output = ufo_group_pop_output_buffer (group, &requisition);
-                            }
-                        } while (active);
-
-                        ufo_group_finish (group);
-                    }
-                    break;
-            }
+                    output = ufo_group_pop_output_buffer (group, &requisition);
+                }
+            } while (active);
         }
+
+        if (!active)
+            ufo_group_finish (group);
     }
 
-    g_message ("`%s' finished", G_OBJECT_TYPE_NAME (tld->task));
+    // g_message ("`%s' finished: CPU: %3.5fs GPU: %3.5fs IO: %3.5fs",
+    //            G_OBJECT_TYPE_NAME (tld->task),
+    //            ufo_profiler_elapsed (profiler, UFO_PROFILER_TIMER_CPU),
+    //            ufo_profiler_elapsed (profiler, UFO_PROFILER_TIMER_GPU),
+    //            ufo_profiler_elapsed (profiler, UFO_PROFILER_TIMER_IO));
 
+    g_object_unref (profiler);
     return NULL;
 }
 
@@ -390,6 +453,7 @@ cleanup_task_local_data (TaskLocalData **tlds,
 {
     for (guint i = 0; i < n; i++) {
         TaskLocalData *tld = tlds[i];
+
         g_free (tld->in_params);
         g_free (tld->finished);
         g_free (tld);
@@ -414,6 +478,7 @@ setup_tasks (UfoSchedulerPrivate *priv,
 
     for (guint i = 0; i < n_nodes; i++) {
         UfoNode *node;
+        UfoProfiler *profiler;
         TaskLocalData *tld;
 
         node = g_list_nth_data (nodes, i);
@@ -424,6 +489,9 @@ setup_tasks (UfoSchedulerPrivate *priv,
         ufo_task_setup (UFO_TASK (node), priv->resources, error);
         ufo_task_get_structure (UFO_TASK (node), &tld->n_inputs, &tld->in_params, &tld->mode);
 
+        profiler = ufo_task_node_get_profiler (UFO_TASK_NODE (node));
+        ufo_profiler_enable_tracing (profiler, priv->trace);
+
         tld->finished = g_new0 (gboolean, tld->n_inputs);
 
         if (error && *error != NULL)
@@ -481,6 +549,177 @@ setup_groups (UfoSchedulerPrivate *priv,
     return groups;
 }
 
+static gboolean
+correct_connections (UfoTaskGraph *graph,
+                     GError **error)
+{
+    GList *nodes;
+    gboolean result = TRUE;
+
+    nodes = ufo_graph_get_nodes (UFO_GRAPH (graph));
+
+    for (GList *it = g_list_first (nodes); it != NULL; it = g_list_next (it)) {
+        UfoTaskNode *node;
+        UfoInputParam *in_params;
+        guint n_inputs;
+        UfoTaskMode mode;
+        UfoGroup *group;
+
+        node = UFO_TASK_NODE (it->data);
+        ufo_task_get_structure (UFO_TASK (node), &n_inputs, &in_params, &mode);
+        group = ufo_task_node_get_out_group (node);
+
+        if (((mode == UFO_TASK_MODE_GENERATOR) || (mode == UFO_TASK_MODE_REDUCTOR)) &&
+            ufo_group_get_num_targets (group) < 1) {
+            g_set_error (error, UFO_SCHEDULER_ERROR, UFO_SCHEDULER_ERROR_SETUP,
+                         "No outgoing node for `%s'",
+                         ufo_task_node_get_unique_name (node));
+            result = FALSE;
+            break;
+        }
+    }
+
+    g_list_free (nodes);
+    return result;
+}
+
+static void
+replicate_task_graph (UfoTaskGraph *graph,
+                      UfoArchGraph *arch)
+{
+    GList *remotes;
+    guint n_graphs;
+    guint index = 1;
+
+    remotes = ufo_arch_graph_get_remote_nodes (arch);
+    n_graphs = g_list_length (remotes) + 1;
+
+    for (GList *it = g_list_first (remotes); it != NULL; it = g_list_next (it)) {
+        UfoRemoteNode *node;
+        gchar *json;
+
+        /* Set partition index for the remote task graph */
+        ufo_task_graph_set_partition (graph, index++, n_graphs);
+        json = ufo_task_graph_get_json_data (graph, NULL);
+        node = UFO_REMOTE_NODE (it->data);
+        ufo_remote_node_send_json (node, UFO_REMOTE_MODE_REPLICATE, json);
+        g_free (json);
+    }
+
+    /* Set partition index for the local task graph */
+    ufo_task_graph_set_partition (graph, 0, n_graphs);
+    g_list_free (remotes);
+}
+
+static void
+propagate_partition (UfoTaskGraph *graph)
+{
+    GList *nodes;
+    guint index;
+    guint total;
+
+    ufo_task_graph_get_partition (graph, &index, &total);
+    nodes = ufo_graph_get_nodes (UFO_GRAPH (graph));
+
+    for (GList *it = g_list_first (nodes); it != NULL; it = g_list_next (it))
+        ufo_task_node_set_partition (UFO_TASK_NODE (it->data), index, total);
+
+    g_list_free (nodes);
+}
+
+typedef struct {
+    UfoTraceEvent *event;
+    UfoTaskNode *node;
+    gdouble timestamp;
+} SortedEvent;
+
+static gint
+compare_event (const SortedEvent *a,
+               const SortedEvent *b,
+               gpointer user_data)
+{
+    return (gint) (a->timestamp - b->timestamp);
+}
+
+static GList *
+get_sorted_event_trace (TaskLocalData **tlds,
+                        guint n_nodes)
+{
+    GList *sorted = NULL;
+
+    for (guint i = 0; i < n_nodes; i++) {
+        UfoTaskNode *node;
+        UfoProfiler *profiler;
+        GList *events;
+
+        node = UFO_TASK_NODE (tlds[i]->task);
+        profiler = ufo_task_node_get_profiler (node);
+        events = ufo_profiler_get_trace_events (profiler);
+
+        for (GList *it = g_list_first (events); it != NULL; it = g_list_next (it)) {
+            UfoTraceEvent *event;
+            SortedEvent *new_event;
+
+            event = (UfoTraceEvent *) it->data;
+            new_event = g_new0 (SortedEvent, 1);
+            new_event->event = event;
+            new_event->timestamp = event->timestamp;
+            new_event->node = node;
+            sorted = g_list_insert_sorted_with_data (sorted, new_event,
+                                                     (GCompareDataFunc) compare_event, NULL);
+        }
+    }
+
+    return sorted;
+}
+
+static void
+write_traces (TaskLocalData **tlds,
+              guint n_nodes)
+{
+    FILE *fp;
+    gchar *filename;
+    guint pid;
+    GList *sorted;
+
+    sorted = get_sorted_event_trace (tlds, n_nodes);
+    pid = (guint) getpid ();
+    filename = g_strdup_printf (".trace.%i.json", pid);
+    fp = fopen (filename, "w");
+    fprintf (fp, "{ \"traceEvents\": [");
+
+    for (GList *it = g_list_first (sorted); it != NULL; it = g_list_next (it)) {
+        SortedEvent *sorted_event;
+        UfoTraceEvent *event;
+        gchar *name;
+
+        sorted_event = (SortedEvent *) it->data;
+        event = sorted_event->event;
+        name = g_strdup_printf ("%s-%p", G_OBJECT_TYPE_NAME (sorted_event->node),
+                                (gpointer) sorted_event->node);
+
+        fprintf (fp, "{\"cat\":\"f\",\"ph\": \"%s\", \"ts\": %.1f, \"pid\": %i, \"tid\": \"%s\",\"name\": \"%s\", \"args\": {}}\n",
+                     event->type, event->timestamp, pid, name, event->name);
+        if (g_list_next (it) != NULL)
+            fprintf (fp, ",");
+
+        g_free (name);
+    }
+
+    fprintf (fp, "] }");
+    fclose (fp);
+
+    g_list_foreach (sorted, (GFunc) g_free, NULL);
+    g_list_free (sorted);
+}
+
+static void
+join_threads (GThread **threads, guint n_threads)
+{
+    for (guint i = 0; i < n_threads; i++)
+        g_thread_join (threads[i]);
+}
+
 void
 ufo_scheduler_run (UfoScheduler *scheduler,
                    UfoTaskGraph *task_graph,
@@ -497,12 +736,25 @@ ufo_scheduler_run (UfoScheduler *scheduler,
     g_return_if_fail (UFO_IS_SCHEDULER (scheduler));
     priv = scheduler->priv;
 
+    if (priv->construct_error != NULL) {
+        if (error)
+            *error = g_error_copy (priv->construct_error);
+        return;
+    }
+
     arch_graph = UFO_ARCH_GRAPH (ufo_arch_graph_new (priv->resources,
                                                      priv->remotes));
 
-    if (priv->expand)
-        ufo_task_graph_expand (task_graph, arch_graph);
+    if (priv->mode == UFO_REMOTE_MODE_REPLICATE) {
+        replicate_task_graph (task_graph, arch_graph);
+    }
+
+    if (priv->expand) {
+        gboolean expand_remote = priv->mode == UFO_REMOTE_MODE_STREAM;
+        ufo_task_graph_expand (task_graph, arch_graph, expand_remote);
+    }
 
+    propagate_partition (task_graph);
     ufo_task_graph_map (task_graph, arch_graph);
 
     /* Prepare task structures */
@@ -512,6 +764,10 @@ ufo_scheduler_run (UfoScheduler *scheduler,
         return;
 
     groups = setup_groups (priv, task_graph);
+
+    if (!correct_connections (task_graph, error))
+        return;
+
     n_nodes = ufo_graph_get_num_nodes (UFO_GRAPH (task_graph));
     threads = g_new0 (GThread *, n_nodes);
     timer = g_timer_new ();
@@ -524,14 +780,32 @@ ufo_scheduler_run (UfoScheduler *scheduler,
             return;
     }
 
-    /* Wait for threads to finish */
-    for (guint i = 0; i < n_nodes; i++)
-        g_thread_join (threads[i]);
+#ifdef HAVE_PYTHON
+    if (Py_IsInitialized ()) {
+        Py_BEGIN_ALLOW_THREADS
+
+        join_threads (threads, n_nodes);
 
-    g_print ("Processing finished after %3.5fs\n", g_timer_elapsed (timer, NULL));
+        Py_END_ALLOW_THREADS
+    }
+    else {
+        join_threads (threads, n_nodes);
+    }
+#else
+    join_threads (threads, n_nodes);
+#endif
+
+#ifdef HAVE_PYTHON
+    if (Py_IsInitialized ())
+#endif
+
+    g_message ("Processing finished after %3.5fs", g_timer_elapsed (timer, NULL));
     g_timer_destroy (timer);
 
     /* Cleanup */
+    if (priv->trace)
+        write_traces (tlds, n_nodes);
+
     cleanup_task_local_data (tlds, n_nodes);
     g_list_foreach (groups, (GFunc) g_object_unref, NULL);
     g_list_free (groups);
@@ -587,6 +861,10 @@ ufo_scheduler_set_property (GObject      *object,
             priv->expand = g_value_get_boolean (value);
             break;
 
+        case PROP_ENABLE_TRACING:
+            priv->trace = g_value_get_boolean (value);
+            break;
+
         default:
             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
             break;
@@ -606,6 +884,10 @@ ufo_scheduler_get_property (GObject      *object,
             g_value_set_boolean (value, priv->expand);
             break;
 
+        case PROP_ENABLE_TRACING:
+            g_value_set_boolean (value, priv->trace);
+            break;
+
         default:
             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
             break;
@@ -618,7 +900,8 @@ ufo_scheduler_constructed (GObject *object)
     UfoSchedulerPrivate *priv;
 
     priv = UFO_SCHEDULER_GET_PRIVATE (object);
-    priv->resources = ufo_resources_new (priv->config);
+    priv->resources = ufo_resources_new (priv->config,
+                                         &priv->construct_error);
 }
 
 static void
@@ -648,6 +931,7 @@ ufo_scheduler_finalize (GObject *object)
 
     priv = UFO_SCHEDULER_GET_PRIVATE (object);
 
+    g_clear_error (&priv->construct_error);
     g_list_foreach (priv->remotes, (GFunc) g_free, NULL);
     g_list_free (priv->remotes);
     priv->remotes = NULL;
@@ -655,6 +939,41 @@ ufo_scheduler_finalize (GObject *object)
     G_OBJECT_CLASS (ufo_scheduler_parent_class)->finalize (object);
 }
 
+static gboolean
+ufo_scheduler_initable_init (GInitable *initable,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+    UfoScheduler *scheduler;
+    UfoSchedulerPrivate *priv;
+
+    g_return_val_if_fail (UFO_IS_SCHEDULER (initable), FALSE);
+
+    if (cancellable != NULL) {
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                             "Cancellable initialization not supported");
+        return FALSE;
+    }
+
+    scheduler = UFO_SCHEDULER (initable);
+    priv = scheduler->priv;
+
+    if (priv->construct_error != NULL) {
+        if (error)
+            *error = g_error_copy (priv->construct_error);
+
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void
+ufo_scheduler_initable_iface_init (GInitableIface *iface)
+{
+    iface->init = ufo_scheduler_initable_init;
+}
+
 static void
 ufo_scheduler_class_init (UfoSchedulerClass *klass)
 {
@@ -672,6 +991,13 @@ ufo_scheduler_class_init (UfoSchedulerClass *klass)
                               TRUE,
                               G_PARAM_READWRITE);
 
+    properties[PROP_ENABLE_TRACING] =
+        g_param_spec_boolean ("enable-tracing",
+                              "Enable and write profile traces",
+                              "Enable and write profile traces",
+                              FALSE,
+                              G_PARAM_READWRITE);
+
     properties[PROP_REMOTES] =
         g_param_spec_value_array ("remotes",
                                   "List containing remote addresses",
@@ -698,7 +1024,10 @@ ufo_scheduler_init (UfoScheduler *scheduler)
 
     scheduler->priv = priv = UFO_SCHEDULER_GET_PRIVATE (scheduler);
     priv->expand = TRUE;
+    priv->trace = FALSE;
     priv->config = NULL;
     priv->resources = NULL;
     priv->remotes = NULL;
+    priv->construct_error = NULL;
+    priv->mode = UFO_REMOTE_MODE_STREAM;
 }
diff --git a/ufo/ufo-scheduler.h b/ufo/ufo-scheduler.h
index f3ae564..562d054 100644
--- a/ufo/ufo-scheduler.h
+++ b/ufo/ufo-scheduler.h
@@ -26,6 +26,7 @@
 
 #include <ufo/ufo-config.h>
 #include <ufo/ufo-task-graph.h>
+#include <ufo/ufo-remote-node.h>
 
 G_BEGIN_DECLS
 
@@ -36,10 +37,16 @@ G_BEGIN_DECLS
 #define UFO_IS_SCHEDULER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_SCHEDULER))
 #define UFO_SCHEDULER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UFO_TYPE_SCHEDULER, UfoSchedulerClass))
 
+#define UFO_SCHEDULER_ERROR            ufo_scheduler_error_quark()
+
 typedef struct _UfoScheduler           UfoScheduler;
 typedef struct _UfoSchedulerClass      UfoSchedulerClass;
 typedef struct _UfoSchedulerPrivate    UfoSchedulerPrivate;
 
+typedef enum {
+    UFO_SCHEDULER_ERROR_SETUP
+} UfoSchedulerError;
+
 /**
  * UfoScheduler:
  *
@@ -64,16 +71,18 @@ struct _UfoSchedulerClass {
     GObjectClass parent_class;
 };
 
-UfoScheduler* ufo_scheduler_new             (UfoConfig     *config,
-                                             GList         *remotes);
-void          ufo_scheduler_run             (UfoScheduler  *scheduler,
-                                             UfoTaskGraph  *task_graph,
-                                             GError**       error);
-gpointer      ufo_scheduler_get_context     (UfoScheduler  *scheduler);
-void          ufo_scheduler_set_task_expansion
-                                            (UfoScheduler  *scheduler,
-                                             gboolean       split);
-GType         ufo_scheduler_get_type        (void);
+UfoScheduler* ufo_scheduler_new                 (UfoConfig     *config,
+                                                 GList         *remotes);
+void          ufo_scheduler_run                 (UfoScheduler  *scheduler,
+                                                 UfoTaskGraph  *task_graph,
+                                                 GError**       error);
+gpointer      ufo_scheduler_get_context         (UfoScheduler  *scheduler);
+void          ufo_scheduler_set_task_expansion  (UfoScheduler  *scheduler,
+                                                 gboolean       split);
+void          ufo_scheduler_set_remote_mode     (UfoScheduler  *scheduler,
+                                                 UfoRemoteMode  mode);
+GType         ufo_scheduler_get_type            (void);
+GQuark        ufo_scheduler_error_quark         (void);
 
 G_END_DECLS
 
diff --git a/ufo/ufo-task-graph.c b/ufo/ufo-task-graph.c
index 3fc0c79..b76c78f 100644
--- a/ufo/ufo-task-graph.c
+++ b/ufo/ufo-task-graph.c
@@ -43,6 +43,8 @@ struct _UfoTaskGraphPrivate {
     GHashTable *prop_sets;
     GHashTable *json_nodes;
     GList *remote_tasks;
+    guint index;
+    guint total;
 };
 
 typedef enum {
@@ -55,10 +57,16 @@ static void handle_json_prop_set    (JsonObject *, const gchar *, JsonNode *, gp
 static void handle_json_single_prop (JsonObject *, const gchar *, JsonNode *, gpointer user);
 static void handle_json_task_edge   (JsonArray *, guint, JsonNode *, gpointer);
 static gboolean handle_json_task_node (JsonNode *, UfoTaskGraphPrivate *priv, GError **error);
-static void add_task_node_to_json_array (UfoNode *, JsonArray *);
+static void add_task_node_to_json_array (UfoTaskNode *, JsonArray *);
 static JsonObject *json_object_from_ufo_node (UfoNode *node);
 static JsonNode *get_json_representation (UfoTaskGraph *, GError **);
 
+/*
+ * ChangeLog:
+ * - 1.1: Add "index" and "total" keys to the root object
+ */
+static const gchar *JSON_API_VERSION = "1.1";
+
 /**
  * UfoTaskGraphError:
  * @UFO_TASK_GRAPH_ERROR_JSON_KEY: Key is not found in JSON
@@ -94,6 +102,8 @@ read_json (UfoTaskGraph *graph,
            GError **error)
 {
     JsonParser *json_parser;
+    JsonNode *json_root;
+    JsonObject *object;
     GError *tmp_error = NULL;
 
     json_parser = json_parser_new ();
@@ -122,7 +132,20 @@ read_json (UfoTaskGraph *graph,
     graph->priv->manager = manager;
     g_object_ref (manager);
 
-    add_nodes_from_json (graph, json_parser_get_root (json_parser), error);
+    json_root = json_parser_get_root (json_parser);
+    object = json_node_get_object (json_root);
+
+    if (json_object_has_member (object, "index") &&
+        json_object_has_member (object, "total")) {
+        guint index = (guint) json_object_get_int_member (object, "index");
+        guint total = (guint) json_object_get_int_member (object, "total");
+        ufo_task_graph_set_partition (graph, index, total);
+    }
+    else {
+        g_warning ("JSON does not define `index' and `total' keys");
+    }
+
+    add_nodes_from_json (graph, json_root, error);
     g_object_unref (json_parser);
 }
 
@@ -212,14 +235,37 @@ get_json_representation (UfoTaskGraph *graph,
         g_list_free (successors);
     }
 
+    json_object_set_string_member (root_object, "version", JSON_API_VERSION);
     json_object_set_array_member (root_object, "nodes", nodes);
     json_object_set_array_member (root_object, "edges", edges);
+    json_object_set_int_member (root_object, "index", graph->priv->index);
+    json_object_set_int_member (root_object, "total", graph->priv->total);
+
     json_node_set_object (root_node, root_object);
     g_list_free (task_nodes);
 
     return root_node;
 }
 
+static JsonGenerator *
+task_graph_to_generator (UfoTaskGraph *graph,
+                         GError **error)
+{
+    JsonNode *root_node;
+    JsonGenerator *generator;
+
+    root_node = get_json_representation (graph, error);
+
+    if (error != NULL && *error != NULL)
+        return NULL;
+
+    generator = json_generator_new ();
+    json_generator_set_root (generator, root_node);
+    json_node_free (root_node);
+
+    return generator;
+}
+
 /**
  * ufo_task_graph_save_to_json:
  * @graph: A #UfoTaskGraph.
@@ -233,20 +279,31 @@ ufo_task_graph_save_to_json (UfoTaskGraph *graph,
                              const gchar *filename,
                              GError **error)
 {
-    JsonNode *root_node;
     JsonGenerator *generator;
 
-    root_node = get_json_representation (graph, error);
+    generator = task_graph_to_generator (graph, error);
 
-    if (error != NULL && *error != NULL)
-        return;
+    if (generator != NULL) {
+        json_generator_to_file (generator, filename, error);
+        g_object_unref (generator);
+    }
+}
 
-    generator = json_generator_new ();
-    json_generator_set_root (generator, root_node);
-    json_generator_to_file (generator, filename, error);
+gchar *
+ufo_task_graph_get_json_data (UfoTaskGraph *graph,
+                              GError **error)
+{
+    JsonGenerator *generator;
+    gchar *json = NULL;
 
-    json_node_free (root_node);
-    g_object_unref (generator);
+    generator = task_graph_to_generator (graph, error);
+
+    if (generator != NULL) {
+        json = json_generator_to_data (generator, NULL);
+        g_object_unref (generator);
+    }
+
+    return json;
 }
 
 static gboolean
@@ -260,7 +317,7 @@ build_remote_graph (UfoTaskGraph *remote_graph,
                     GList *first,
                     GList *last)
 {
-    UfoTaskNode *node;
+    UfoTaskNode *node = NULL;
     UfoTaskNode *predecessor = NULL;
 
     for (GList *it = g_list_next (first); it != last; it = g_list_next (it)) {
@@ -272,6 +329,7 @@ build_remote_graph (UfoTaskGraph *remote_graph,
         predecessor = node;
     }
 
+    g_assert (node != NULL);
     return node;
 }
 
@@ -284,18 +342,11 @@ create_remote_tasks (UfoTaskGraph *task_graph,
 {
     UfoTaskGraphPrivate *priv;
     UfoTaskNode *task;
-    JsonNode *root;
-    JsonGenerator *generator;
     gchar *json;
-    gsize size;
-
-    root = get_json_representation (remote_graph, NULL);
-    generator = json_generator_new ();
-    json_generator_set_root (generator, root);
-    json = json_generator_to_data (generator, &size);
 
     priv = task_graph->priv;
-    ufo_remote_node_send_json (remote, json, size);
+    json = ufo_task_graph_get_json_data (remote_graph, NULL);
+    ufo_remote_node_send_json (remote, UFO_REMOTE_MODE_STREAM, json);
 
     task = UFO_TASK_NODE (ufo_remote_task_new ());
     priv->remote_tasks = g_list_append (priv->remote_tasks, task);
@@ -305,8 +356,6 @@ create_remote_tasks (UfoTaskGraph *task_graph,
     ufo_task_graph_connect_nodes (task_graph, task, last);
 
     g_free (json);
-    json_node_free (root);
-    g_object_unref (generator);
 }
 
 static void
@@ -407,6 +456,7 @@ find_longest_path (GList *paths)
  * ufo_task_graph_expand:
  * @task_graph: A #UfoTaskGraph
  * @arch_graph: A #UfoArchGraph
+ * @expand_remote: %TRUE if remote nodes should be inserted
  *
  * Expands @task_graph in a way that most of the resources in @arch_graph can be
  * occupied. In the simple pipeline case, the longest possible GPU paths are
@@ -414,7 +464,8 @@ find_longest_path (GList *paths)
  */
 void
 ufo_task_graph_expand (UfoTaskGraph *task_graph,
-                      UfoArchGraph *arch_graph)
+                       UfoArchGraph *arch_graph,
+                       gboolean expand_remote)
 {
     GList *paths;
     GList *path;
@@ -428,16 +479,21 @@ ufo_task_graph_expand (UfoTaskGraph *task_graph,
     path = find_longest_path (paths);
 
     if (path != NULL) {
-        GList *remotes;
         guint n_gpus;
-        guint n_remotes;
 
-        remotes = ufo_arch_graph_get_remote_nodes (arch_graph);
-        n_remotes = g_list_length (remotes);
+        if (expand_remote) {
+            GList *remotes;
+            guint n_remotes;
+
+            remotes = ufo_arch_graph_get_remote_nodes (arch_graph);
+            n_remotes = g_list_length (remotes);
 
-        if (n_remotes > 0) {
-            g_debug ("Expand for %i remote nodes", n_remotes);
-            expand_remotes (task_graph, remotes, path);
+            if (n_remotes > 0) {
+                g_debug ("Expand for %i remote nodes", n_remotes);
+                expand_remotes (task_graph, remotes, path);
+            }
+
+            g_list_free (remotes);
         }
 
         n_gpus = ufo_arch_graph_get_num_gpus (arch_graph);
@@ -445,8 +501,6 @@ ufo_task_graph_expand (UfoTaskGraph *task_graph,
 
         for (guint i = 1; i < n_gpus; i++)
             ufo_graph_expand (UFO_GRAPH (task_graph), path);
-
-        g_list_free (remotes);
     }
 
     g_list_foreach (paths, (GFunc) g_list_free, NULL);
@@ -493,7 +547,9 @@ map_proc_node (UfoGraph *graph,
 
     for (GList *it = g_list_first (successors); it != NULL; it = g_list_next (it)) {
         map_proc_node (graph, UFO_NODE (it->data), proc_index, gpu_nodes);
-        proc_index = (proc_index + 1) % n_gpus;
+
+        if (!UFO_IS_REMOTE_TASK (UFO_NODE (it->data)))
+            proc_index = (proc_index + 1) % n_gpus;
     }
 
     g_list_free (successors);
@@ -560,6 +616,27 @@ ufo_task_graph_connect_nodes_full (UfoTaskGraph *graph,
     ufo_graph_connect_nodes (UFO_GRAPH (graph), UFO_NODE (n1), UFO_NODE (n2), GINT_TO_POINTER (input));
 }
 
+void
+ufo_task_graph_set_partition (UfoTaskGraph *graph,
+                              guint index,
+                              guint total)
+{
+    g_return_if_fail (UFO_IS_TASK_GRAPH (graph));
+    g_assert (index < total);
+    graph->priv->index = index;
+    graph->priv->total = total;
+}
+
+void
+ufo_task_graph_get_partition (UfoTaskGraph *graph,
+                              guint *index,
+                              guint *total)
+{
+    g_return_if_fail (UFO_IS_TASK_GRAPH (graph));
+    *index = graph->priv->index;
+    *total = graph->priv->total;
+}
+
 static void
 add_nodes_from_json (UfoTaskGraph *graph,
                      JsonNode *root,
@@ -601,7 +678,7 @@ handle_json_task_node (JsonNode *element,
                        UfoTaskGraphPrivate *priv,
                        GError **error)
 {
-    UfoNode *plugin;
+    UfoTaskNode *plugin;
     JsonObject *object;
     GError *tmp_error = NULL;
     const gchar *name;
@@ -618,6 +695,7 @@ handle_json_task_node (JsonNode *element,
 
     plugin_name = json_object_get_string_member (object, "plugin");
     plugin = ufo_plugin_manager_get_task (priv->manager, plugin_name, &tmp_error);
+    ufo_task_node_set_plugin_name (plugin, plugin_name);
 
     if (tmp_error != NULL) {
         g_propagate_error (error, tmp_error);
@@ -711,6 +789,12 @@ handle_json_task_edge (JsonArray *array,
     from_node = g_hash_table_lookup (priv->json_nodes, from_name);
     to_node = g_hash_table_lookup (priv->json_nodes, to_name);
 
+    if (from_node == NULL)
+        g_error ("No filter `%s' defined", from_name);
+
+    if (to_node == NULL)
+        g_error ("No filter `%s' defined", to_name);
+
     ufo_task_graph_connect_nodes_full (graph, from_node, to_node, to_port);
 
     if (error != NULL)
@@ -739,25 +823,26 @@ handle_json_single_prop (JsonObject *object,
                          gpointer user)
 {
     GValue val = {0,};
-    json_node_get_value (node, &val);
-    g_object_set_property (G_OBJECT(user), name, &val);
+    if (!JSON_NODE_HOLDS_NULL (node)) {
+        json_node_get_value (node, &val);
+        g_object_set_property (G_OBJECT(user), name, &val);
+    }
 }
 
 static void
-add_task_node_to_json_array (UfoNode *node, JsonArray *array)
+add_task_node_to_json_array (UfoTaskNode *node, JsonArray *array)
 {
     JsonObject *node_object;
     JsonNode *prop_node;
 
     node_object = json_object_new ();
-
-    json_object_set_string_member (node_object,
-                                   "plugin",
-                                   ufo_task_node_get_plugin_name (UFO_TASK_NODE (node)));
-
-    json_object_set_string_member (node_object,
-                                   "name",
-                                   ufo_task_node_get_unique_name (UFO_TASK_NODE (node)));
+    const gchar *plugin_name = ufo_task_node_get_plugin_name (node);
+    g_assert (plugin_name != NULL);
+    json_object_set_string_member (node_object, "plugin", plugin_name);
+                                   
+    const gchar *name = ufo_task_node_get_unique_name (node);
+    g_assert (name != NULL);
+    json_object_set_string_member (node_object, "name", name);
 
     prop_node = json_gobject_serialize (G_OBJECT (node));
     json_object_set_member (node_object, "properties", prop_node);
@@ -770,13 +855,11 @@ json_object_from_ufo_node (UfoNode *node)
     JsonObject *object;
 
     object = json_object_new ();
-    json_object_set_string_member (object,
-                                   "name",
-                                   ufo_task_node_get_unique_name (UFO_TASK_NODE (node)));
+    const gchar *unique_name = ufo_task_node_get_unique_name (UFO_TASK_NODE (node));
+    json_object_set_string_member (object, "name", unique_name);
     return object;
 }
 
-
 static void
 ufo_task_graph_dispose (GObject *object)
 {
@@ -839,8 +922,6 @@ ufo_task_graph_init (UfoTaskGraph *self)
 
     priv->prop_sets = g_hash_table_new_full (g_str_hash, g_str_equal,
                                              g_free, (GDestroyNotify) json_object_unref);
-
-    /* Maybe we should define a specific task node type from which all tasks
-     * must inherit */
-    ufo_graph_register_node_type (UFO_GRAPH (self), UFO_TYPE_NODE);
+    priv->index = 0;
+    priv->total = 1;
 }
diff --git a/ufo/ufo-task-graph.h b/ufo/ufo-task-graph.h
index 10b9c1a..75cb1b3 100644
--- a/ufo/ufo-task-graph.h
+++ b/ufo/ufo-task-graph.h
@@ -84,10 +84,13 @@ void         ufo_task_graph_read_from_data      (UfoTaskGraph       *graph,
 void         ufo_task_graph_save_to_json        (UfoTaskGraph       *graph,
                                                  const gchar        *filename,
                                                  GError            **error);
+gchar       *ufo_task_graph_get_json_data       (UfoTaskGraph       *graph,
+                                                 GError            **error);
 void         ufo_task_graph_map                 (UfoTaskGraph       *task_graph,
                                                  UfoArchGraph       *arch_graph);
 void         ufo_task_graph_expand              (UfoTaskGraph       *task_graph,
-                                                 UfoArchGraph       *arch_graph);
+                                                 UfoArchGraph       *arch_graph,
+                                                 gboolean            expand_remote);
 void         ufo_task_graph_connect_nodes       (UfoTaskGraph       *graph,
                                                  UfoTaskNode        *n1,
                                                  UfoTaskNode        *n2);
@@ -96,6 +99,12 @@ void         ufo_task_graph_connect_nodes_full  (UfoTaskGraph       *graph,
                                                  UfoTaskNode        *n2,
                                                  guint               input);
 void         ufo_task_graph_fuse                (UfoTaskGraph       *task_graph);
+void         ufo_task_graph_set_partition       (UfoTaskGraph       *task_graph,
+                                                 guint               index,
+                                                 guint               total);
+void         ufo_task_graph_get_partition       (UfoTaskGraph       *task_graph,
+                                                 guint              *index,
+                                                 guint              *total);
 GType        ufo_task_graph_get_type            (void);
 GQuark       ufo_task_graph_error_quark         (void);
 
diff --git a/ufo/ufo-task-iface.h b/ufo/ufo-task-iface.h
index 1199627..314a9e5 100644
--- a/ufo/ufo-task-iface.h
+++ b/ufo/ufo-task-iface.h
@@ -48,18 +48,27 @@ typedef enum {
 
 /**
  * UfoTaskMode:
- * @UFO_TASK_MODE_SINGLE: one-by-one processing
- * @UFO_TASK_MODE_REDUCE: receive fininite stream and generate a reduced stream
- * @UFO_TASK_MODE_GENERATE: do not receive any data but produce a stream.
+ * @UFO_TASK_MODE_PROCESSOR: one-by-one processing
+ * @UFO_TASK_MODE_GENERATOR: do not receive any data but produce a stream.
+ * @UFO_TASK_MODE_REDUCTOR: receive fininite stream and generate a reduced stream
  *
  * Task modes describe how a task operates considering the input data.
  */
 typedef enum {
-    UFO_TASK_MODE_SINGLE,
-    UFO_TASK_MODE_REDUCE,
-    UFO_TASK_MODE_GENERATE
+    UFO_TASK_MODE_PROCESSOR,
+    UFO_TASK_MODE_GENERATOR,
+    UFO_TASK_MODE_REDUCTOR,
 } UfoTaskMode;
 
+typedef gboolean (*UfoTaskProcessFunc) (UfoTask *task,
+                                        UfoBuffer **inputs,
+                                        UfoBuffer *output,
+                                        UfoRequisition *requisition);
+
+typedef gboolean (*UfoTaskGenerateFunc) (UfoTask *task,
+                                         UfoBuffer *output,
+                                         UfoRequisition *requisition);
+
 /**
  * UfoInputParam:
  * @n_dims: Number of dimensions
diff --git a/ufo/ufo-task-node.c b/ufo/ufo-task-node.c
index 6f60db6..23f1fa4 100644
--- a/ufo/ufo-task-node.c
+++ b/ufo/ufo-task-node.c
@@ -32,9 +32,12 @@ struct _UfoTaskNodePrivate {
     UfoSendPattern   pattern;
     UfoNode         *proc_node;
     UfoGroup        *out_group;
+    UfoProfiler     *profiler;
     GList           *in_groups[16];
     GList           *current[16];
     gint             n_expected[16];
+    guint            index;
+    guint            total;
 };
 
 void
@@ -56,14 +59,14 @@ ufo_task_node_set_plugin_name (UfoTaskNode *task_node,
 const gchar *
 ufo_task_node_get_plugin_name (UfoTaskNode *task_node)
 {
-    g_return_val_if_fail (UFO_IS_TASK_NODE (task_node), NULL);
+    g_assert (UFO_IS_TASK_NODE (task_node));
     return task_node->priv->plugin;
 }
 
 const gchar *
 ufo_task_node_get_unique_name (UfoTaskNode *task_node)
 {
-    g_return_val_if_fail (UFO_IS_TASK_NODE (task_node), NULL);
+    g_assert (UFO_IS_TASK_NODE (task_node));
     return task_node->priv->unique;
 }
 
@@ -177,6 +180,34 @@ ufo_task_node_set_proc_node (UfoTaskNode *task_node,
     task_node->priv->proc_node = proc_node;
 }
 
+void
+ufo_task_node_set_profiler (UfoTaskNode *node,
+                            UfoProfiler *profiler)
+{
+    g_return_if_fail (UFO_IS_TASK_NODE (node));
+
+    if (node->priv->profiler)
+        g_object_unref (node->priv->profiler);
+
+    g_object_ref (profiler);
+    node->priv->profiler = profiler;
+}
+
+/**
+ * ufo_task_node_get_profiler:
+ * @node: A #UfoTaskNode
+ *
+ * Get the associated profiler of @node.
+ *
+ * Return value: (transfer full): A #UfoProfiler object.
+ */
+UfoProfiler *
+ufo_task_node_get_profiler (UfoTaskNode *node)
+{
+    g_return_val_if_fail (UFO_IS_TASK_NODE (node), NULL);
+    return node->priv->profiler;
+}
+
 /**
  * ufo_task_node_get_proc_node:
  * @node: A #UfoTaskNode
@@ -192,9 +223,59 @@ ufo_task_node_get_proc_node (UfoTaskNode *node)
     return node->priv->proc_node;
 }
 
+void
+ufo_task_node_set_partition (UfoTaskNode *node,
+                             guint index,
+                             guint total)
+{
+    g_return_if_fail (UFO_IS_TASK_NODE (node));
+    g_assert (index < total);
+    node->priv->index = index;
+    node->priv->total = total;
+}
+
+void
+ufo_task_node_get_partition (UfoTaskNode *node,
+                             guint *index,
+                             guint *total)
+{
+    g_return_if_fail (UFO_IS_TASK_NODE (node));
+    *index = node->priv->index;
+    *total = node->priv->total;
+}
+
+static UfoNode *
+ufo_task_node_copy (UfoNode *node,
+                    GError **error)
+{
+    UfoTaskNode *orig;
+    UfoTaskNode *copy;
+
+    copy = UFO_TASK_NODE (UFO_NODE_CLASS (ufo_task_node_parent_class)->copy (node, error));
+    orig = UFO_TASK_NODE (node);
+
+    copy->priv->pattern = orig->priv->pattern;
+
+    for (guint i = 0; i < 16; i++)
+        copy->priv->n_expected[i] = orig->priv->n_expected[i];
+
+    ufo_task_node_set_plugin_name (copy, orig->priv->plugin);
+
+    return UFO_NODE (copy);
+}
+
 static void
 ufo_task_node_dispose (GObject *object)
 {
+    UfoTaskNodePrivate *priv;
+
+    priv = UFO_TASK_NODE_GET_PRIVATE (object);
+
+    if (priv->profiler) {
+        g_object_unref (priv->profiler);
+        priv->profiler = NULL;
+    }
+
     G_OBJECT_CLASS (ufo_task_node_parent_class)->dispose (object);
 }
 
@@ -214,11 +295,15 @@ static void
 ufo_task_node_class_init (UfoTaskNodeClass *klass)
 {
     GObjectClass *oclass;
+    UfoNodeClass *nclass;
 
     oclass = G_OBJECT_CLASS (klass);
     oclass->dispose = ufo_task_node_dispose;
     oclass->finalize = ufo_task_node_finalize;
 
+    nclass = UFO_NODE_CLASS (klass);
+    nclass->copy = ufo_task_node_copy;
+
     g_type_class_add_private (klass, sizeof(UfoTaskNodePrivate));
 }
 
@@ -231,6 +316,9 @@ ufo_task_node_init (UfoTaskNode *self)
     self->priv->pattern = UFO_SEND_SCATTER;
     self->priv->proc_node = NULL;
     self->priv->out_group = NULL;
+    self->priv->index = 0;
+    self->priv->total = 1;
+    self->priv->profiler = ufo_profiler_new ();
 
     for (guint i = 0; i < 16; i++) {
         self->priv->in_groups[i] = NULL;
diff --git a/ufo/ufo-task-node.h b/ufo/ufo-task-node.h
index 2e6df01..68c8228 100644
--- a/ufo/ufo-task-node.h
+++ b/ufo/ufo-task-node.h
@@ -26,6 +26,7 @@
 
 #include <ufo/ufo-node.h>
 #include <ufo/ufo-group.h>
+#include <ufo/ufo-profiler.h>
 
 G_BEGIN_DECLS
 
@@ -89,6 +90,15 @@ void            ufo_task_node_switch_in_group       (UfoTaskNode    *node,
 void            ufo_task_node_set_proc_node         (UfoTaskNode    *task_node,
                                                      UfoNode        *proc_node);
 UfoNode        *ufo_task_node_get_proc_node         (UfoTaskNode    *node);
+void            ufo_task_node_set_partition         (UfoTaskNode    *node,
+                                                     guint           index,
+                                                     guint           total);
+void            ufo_task_node_get_partition         (UfoTaskNode    *node,
+                                                     guint          *index,
+                                                     guint          *total);
+void            ufo_task_node_set_profiler          (UfoTaskNode    *node,
+                                                     UfoProfiler    *profiler);
+UfoProfiler    *ufo_task_node_get_profiler          (UfoTaskNode    *node);
 GType           ufo_task_node_get_type              (void);
 
 G_END_DECLS
diff --git a/ufo/ufo-zmq-messenger.c b/ufo/ufo-zmq-messenger.c
new file mode 100644
index 0000000..32ba32e
--- /dev/null
+++ b/ufo/ufo-zmq-messenger.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ufo/ufo-zmq-messenger.h>
+#include <zmq.h>
+#include <string.h>
+
+#include "zmq-shim.h"
+
+static void ufo_messenger_interface_init (UfoMessengerIface *iface);
+
+#define UFO_ZMQ_MESSENGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_ZMQ_MESSENGER, UfoZmqMessengerPrivate))
+
+G_DEFINE_TYPE_WITH_CODE (UfoZmqMessenger, ufo_zmq_messenger, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (UFO_TYPE_MESSENGER,
+                                                ufo_messenger_interface_init))
+
+
+struct _UfoZmqMessengerPrivate {
+    gchar *remote_addr;
+    GMutex *mutex;
+    gpointer zmq_socket;
+    gpointer zmq_ctx;
+    UfoMessengerRole role;
+};
+
+/* C99 allows flexible length structs that we use to map
+* arbitrary frame lengths that are transferred via zmq.
+* Note: Sizes of datatypes should be fixed and equal on all plattforms
+* (i.e. don't use a gsize as it has different size on x86 & x86_64)
+*/
+typedef struct _DataFrame {
+    UfoMessageType type;
+    guint64 data_size;
+    // variable length data field
+    char data[];
+} DataFrame;
+
+UfoZmqMessenger *
+ufo_zmq_messenger_new (void)
+{
+    UfoZmqMessenger *msger;
+    msger = UFO_ZMQ_MESSENGER (g_object_new (UFO_TYPE_ZMQ_MESSENGER, NULL));
+
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (msger);
+    priv->zmq_ctx = zmq_ctx_new ();
+
+    return msger;
+}
+
+static void
+validate_zmq_listen_address (gchar *addr)
+{
+    if (!g_str_has_prefix (addr, "tcp://"))
+        g_critical ("address didn't start with tcp:// scheme, which is required currently");
+
+    /* Pitfall: zmq will silently accept hostnames like tcp://localhost:5555
+     * but not bind to it as it treats it like an interface name (like eth0).
+     * We have to use IP addresses instead of DNS names.
+     */
+    gchar *host = g_strdup (&addr[6]);
+    if (!g_ascii_isdigit (host[0]) && host[0] != '*')
+        g_message ("treating address %s as interface device name. Use IP address if supplying a host was intended.", host);
+    g_free (host);
+}
+
+void
+ufo_zmq_messenger_connect (UfoMessenger *msger, gchar *addr, UfoMessengerRole role)
+{
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (msger);
+    g_mutex_lock (priv->mutex);
+
+    priv->remote_addr = g_strdup (addr);
+    priv->role = role;
+
+    if (role == UFO_MESSENGER_CLIENT) {
+        priv->zmq_socket = zmq_socket (priv->zmq_ctx, ZMQ_REQ);
+
+        if (zmq_connect (priv->zmq_socket, priv->remote_addr) == 0) {
+            g_message ("Connected to `%s' via socket=%p",
+                       priv->remote_addr,
+                       priv->zmq_socket);
+        }
+        else {
+            g_warning ("Could not connect to `%s': %s",
+                        addr,
+                        zmq_strerror (errno));
+        }
+    } else if (role == UFO_MESSENGER_SERVER) {
+        validate_zmq_listen_address (priv->remote_addr);
+        priv->zmq_socket = zmq_socket (priv->zmq_ctx, ZMQ_REP);
+
+        gint err = zmq_bind (priv->zmq_socket, priv->remote_addr);
+        if (err < 0)
+            g_critical ("could not bind to address %s", priv->remote_addr);
+    }
+
+    g_mutex_unlock (priv->mutex);
+    return;
+}
+
+void
+ufo_zmq_messenger_disconnect (UfoMessenger *msger)
+{
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (msger);
+
+    g_mutex_lock (priv->mutex);
+
+    if (priv->zmq_socket != NULL) {
+        zmq_close (priv->zmq_socket);
+        priv->zmq_socket = NULL;
+
+        // waits for outstanding messages to be flushed
+        zmq_term (priv->zmq_ctx);
+        g_free (priv->remote_addr);
+    }
+
+    g_mutex_unlock (priv->mutex);
+    return;
+}
+
+UfoMessage *
+ufo_zmq_messenger_send_blocking (UfoMessenger *msger,
+                                 UfoMessage *request_msg,
+                                 GError **error)
+{
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (msger);
+
+    if (request_msg->type == UFO_MESSAGE_ACK && priv->role == UFO_MESSENGER_CLIENT)
+        g_critical ("Clients can't send ACK messages");
+
+    g_mutex_lock (priv->mutex);
+
+    UfoMessage *result = NULL;
+    zmq_msg_t request;
+
+    gsize frame_size = sizeof (DataFrame) + request_msg->data_size;
+    zmq_msg_init_size (&request, frame_size);
+    DataFrame *frame = (DataFrame *) zmq_msg_data (&request);
+
+    frame->data_size = request_msg->data_size;
+    frame->type = request_msg->type;
+    //TODO eliminate extra copying
+    memcpy (frame->data, request_msg->data, request_msg->data_size);
+
+    gint err = zmq_msg_send (&request, priv->zmq_socket, 0);
+    zmq_msg_close (&request);
+
+    if (err < 0) {
+        g_set_error (error, ufo_messenger_error_quark (), zmq_errno (),
+                     "Error sending message via %s: %s",
+                     priv->remote_addr, zmq_strerror (zmq_errno ()));
+        goto finalize;
+    }
+
+    /* if this is an ACK message, don't expect a response
+     * (send_blocking is then most likely being called by the server)
+     */
+    if (request_msg->type == UFO_MESSAGE_ACK) {
+        goto finalize;
+    }
+
+    /* we always need to receive as response as ZMQ
+     * requires REQ/REP/REQ/REP/... scheme
+     */
+    zmq_msg_t reply;
+    zmq_msg_init (&reply);
+
+    err = zmq_msg_recv (&reply, priv->zmq_socket, 0);
+    gint size = zmq_msg_size (&reply);
+    if (err < 0) {
+        g_set_error (error, ufo_messenger_error_quark (), zmq_errno(),
+                     "Could not receive from %s: %s ", priv->remote_addr,
+                     zmq_strerror (zmq_errno ()));
+        goto finalize;
+    }
+
+    DataFrame *resp_frame = (DataFrame *) zmq_msg_data (&reply);
+
+    guint64 expected_size = (guint32) (sizeof (DataFrame) + resp_frame->data_size);
+    if ((guint64) size != expected_size) {
+        g_set_error (error, ufo_messenger_error_quark(),
+                     UFO_MESSENGER_SIZE_MISSMATCH,
+                     "Received unexpected frame size: %d", size);
+        goto finalize;
+    }
+
+    UfoMessage *reply_msg = ufo_message_new (resp_frame->type, resp_frame->data_size);
+    memcpy (reply_msg->data, resp_frame->data, resp_frame->data_size);
+
+    zmq_msg_close (&reply);
+    result = reply_msg;
+    goto finalize;
+
+    finalize:
+        g_mutex_unlock (priv->mutex);
+        return result;
+
+}
+
+UfoMessage *
+ufo_zmq_messenger_recv_blocking (UfoMessenger *msger,
+                                 GError **error)
+{
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (msger);
+    g_assert (priv->role == UFO_MESSENGER_SERVER);
+
+    g_mutex_lock (priv->mutex);
+
+    UfoMessage *result = NULL;
+    zmq_msg_t reply;
+    zmq_msg_init (&reply);
+    gint err = zmq_msg_recv (&reply, priv->zmq_socket, 0);
+    gint size = zmq_msg_size (&reply);
+
+    if (err < 0) {
+        zmq_msg_close (&reply);
+        g_set_error (error, ufo_messenger_error_quark(), zmq_errno(),
+                     "Could not receive from %s: %s ", priv->remote_addr,
+                     zmq_strerror (zmq_errno ()));
+        goto finalize;
+    }
+
+    DataFrame *frame = zmq_msg_data (&reply);
+
+    guint expected_size = (guint) (sizeof (DataFrame) + frame->data_size);
+    if ((guint)size != expected_size) {
+        g_set_error (error, ufo_messenger_error_quark(),
+                     UFO_MESSENGER_SIZE_MISSMATCH,
+                     "Received unexpected frame size: %d, should be: %d",
+                     size, expected_size);
+        goto finalize;
+    }
+
+    UfoMessage *msg = ufo_message_new (frame->type, frame->data_size);
+    memcpy (msg->data, frame->data, frame->data_size);
+
+    zmq_msg_close (&reply);
+    result = msg;
+    goto finalize;
+
+    finalize:
+        g_mutex_unlock (priv->mutex);
+        return result;
+}
+
+static void
+ufo_messenger_interface_init (UfoMessengerIface *iface)
+{
+    iface->connect = ufo_zmq_messenger_connect;
+    iface->disconnect = ufo_zmq_messenger_disconnect;
+    iface->send_blocking = ufo_zmq_messenger_send_blocking;
+    iface->recv_blocking = ufo_zmq_messenger_recv_blocking;
+}
+
+
+static void
+ufo_zmq_messenger_dispose (GObject *object)
+{
+    ufo_zmq_messenger_disconnect (UFO_MESSENGER (object));
+}
+
+static void
+ufo_zmq_messenger_finalize (GObject *object)
+{
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (object);
+
+    if  (priv->zmq_ctx != NULL) {
+        zmq_ctx_destroy (priv->zmq_ctx);
+        priv->zmq_ctx = NULL;
+    }
+
+    g_mutex_free (priv->mutex);
+}
+
+static void
+ufo_zmq_messenger_class_init (UfoZmqMessengerClass *klass)
+{
+    GObjectClass *oclass = G_OBJECT_CLASS (klass);
+    oclass->dispose      = ufo_zmq_messenger_dispose;
+    oclass->finalize     = ufo_zmq_messenger_finalize;
+
+    g_type_class_add_private (klass, sizeof(UfoZmqMessengerPrivate));
+}
+
+static void
+ufo_zmq_messenger_init (UfoZmqMessenger *msger)
+{
+    UfoZmqMessengerPrivate *priv = UFO_ZMQ_MESSENGER_GET_PRIVATE (msger);
+    priv->zmq_socket = NULL;
+    priv->zmq_ctx = NULL;
+    priv->mutex = g_mutex_new ();
+}
diff --git a/ufo/ufo-zmq-messenger.h b/ufo/ufo-zmq-messenger.h
new file mode 100644
index 0000000..19462cb
--- /dev/null
+++ b/ufo/ufo-zmq-messenger.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UFO_ZMQ_MESSENGER_H
+#define __UFO_ZMQ_MESSENGER_H
+
+#include <ufo/ufo-remote-node.h>
+#include <ufo/ufo-messenger-iface.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define UFO_TYPE_ZMQ_MESSENGER             (ufo_zmq_messenger_get_type())
+#define UFO_ZMQ_MESSENGER(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UFO_TYPE_ZMQ_MESSENGER, UfoZmqMessenger))
+#define UFO_IS_ZMQ_MESSENGER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UFO_TYPE_ZMQ_MESSENGER))
+#define UFO_ZMQ_MESSENGER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UFO_TYPE_ZMQ_MESSENGER, UfoZmqMessengerClass))
+#define UFO_IS_ZMQ_MESSENGER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_ZMQ_MESSENGER))
+#define UFO_ZMQ_MESSENGER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UFO_TYPE_ZMQ_MESSENGER, UfoZmqMessengerClass))
+
+typedef struct _UfoZmqMessenger           UfoZmqMessenger;
+typedef struct _UfoZmqMessengerClass      UfoZmqMessengerClass;
+typedef struct _UfoZmqMessengerPrivate    UfoZmqMessengerPrivate;
+
+struct _UfoZmqMessenger {
+    /*< private >*/
+    GObject parent_instance;
+
+    UfoZmqMessengerPrivate *priv;
+};
+
+struct _UfoZmqMessengerClass {
+    /*< private >*/
+    GObjectClass parent_class;
+};
+
+UfoZmqMessenger    *ufo_zmq_messenger_new           (void);
+GType               ufo_zmq_messenger_get_type      (void);
+
+void                ufo_zmq_messenger_connect       (UfoMessenger *msger,
+                                                     gchar *addr,
+                                                     UfoMessengerRole role);
+
+void                ufo_zmq_messenger_disconnect    (UfoMessenger *msg);
+
+UfoMessage         *ufo_zmq_messenger_send_blocking (UfoMessenger *msger,
+                                                     UfoMessage *request,
+                                                     GError **error);
+
+UfoMessage         *ufo_zmq_messenger_recv_blocking (UfoMessenger *msger,
+                                                     GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/ufo/ufo.h b/ufo/ufo.h
index b9b3d8e..e9d8617 100644
--- a/ufo/ufo.h
+++ b/ufo/ufo.h
@@ -29,6 +29,7 @@
 #include <ufo/ufo-cpu-node.h>
 #include <ufo/ufo-cpu-task-iface.h>
 #include <ufo/ufo-dummy-task.h>
+#include <ufo/ufo-daemon.h>
 #include <ufo/ufo-enums.h>
 #include <ufo/ufo-gpu-node.h>
 #include <ufo/ufo-gpu-task-iface.h>
@@ -46,6 +47,8 @@
 #include <ufo/ufo-task-graph.h>
 #include <ufo/ufo-task-iface.h>
 #include <ufo/ufo-task-node.h>
+#include <ufo/ufo-basic-ops.h>
+#include <ufo/ufo-messenger-iface.h>
 
 #undef __UFO_H_INSIDE__
 
diff --git a/ufo/ufo.pc.in b/ufo/ufo.pc.in
index 732f672..bc5c186 100644
--- a/ufo/ufo.pc.in
+++ b/ufo/ufo.pc.in
@@ -1,9 +1,9 @@
-prefix=@UFO_PKG_PREFIX@
-exec_prefix=@UFO_PKG_EXEC_PREFIX@
-libdir=@UFO_PKG_LIBDIR@
-includedir=@UFO_PKG_INCLUDEDIR@
-girdir=@UFO_PKG_GIRDIR@
-typelibdir=@UFO_PKG_TYPELIBDIR@
+prefix=@UFO_PREFIX@
+exec_prefix=@UFO_EPREFIX@
+libdir=@UFO_LIBDIR@
+includedir=@UFO_INCLUDEDIR@
+girdir=@UFO_GIRDIR@
+typelibdir=@UFO_TYPELIBDIR@
 
 Name: @TARNAME@
 Description: @UFO_DESCRIPTION_SUMMARY@
diff --git a/ufo/zmq-shim.h b/ufo/zmq-shim.h
new file mode 100644
index 0000000..7c19973
--- /dev/null
+++ b/ufo/zmq-shim.h
@@ -0,0 +1,22 @@
+#include <zmq.h>
+
+#ifndef ZMQ_DONTWAIT
+#   define ZMQ_DONTWAIT   ZMQ_NOBLOCK
+#endif
+#ifndef ZMQ_RCVHWM
+#   define ZMQ_RCVHWM     ZMQ_HWM
+#endif
+#ifndef ZMQ_SNDHWM
+#   define ZMQ_SNDHWM     ZMQ_HWM
+#endif
+#if ZMQ_VERSION_MAJOR == 2
+#   define more_t int64_t
+#   define zmq_ctx_new() zmq_init (1)
+#   define zmq_ctx_destroy(context) zmq_term (context)
+#   define zmq_msg_send(msg,sock,opt) zmq_send (sock, msg, opt)
+#   define zmq_msg_recv(msg,sock,opt) zmq_recv (sock, msg, opt)
+#   define ZMQ_POLL_MSEC    1000        /* zmq_poll is usec */
+#elif ZMQ_VERSION_MAJOR == 3
+#   define more_t int
+#   define ZMQ_POLL_MSEC    1           /* zmq_poll is msec */
+#endif

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/ufo-core.git



More information about the debian-science-commits mailing list