diff --git a/.github/workflows/build-and-test-bindings.yml b/.github/workflows/build-and-test-bindings.yml
new file mode 100644
index 0000000000..e2d396945f
--- /dev/null
+++ b/.github/workflows/build-and-test-bindings.yml
@@ -0,0 +1,47 @@
+name: Build and Test Bindings Ubuntu 22.04 gcc 11 x64
+
+# Trigger the workflow on push or pull request
+on:
+# push:
+# branches:
+# - master
+# pull_request:
+# types: [opened, reopened, synchronize, converted_to_draft, ready_for_review]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ env:
+ OUTPUT_DIR: '/tmp/out'
+ name: vt-tv bindings build and test
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: CI Variables
+ id: vars
+ run: echo "DOCKER_TAG=$(echo ${{ github.ref }} | cut -d'/' -f3- | sed 's/[^a-z0-9_-]/__/gi')" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v2
+
+ - name: Inspect Builder
+ run: |
+ echo "Name: ${{ steps.buildx.outputs.name }}"
+ echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
+ echo "Status: ${{ steps.buildx.outputs.status }}"
+ echo "Flags: ${{ steps.buildx.outputs.flags }}"
+ echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
+ echo "DOCKER_TAG: ${{ env.DOCKER_TAG }}"
+
+ - name: Build the Docker Image; build and test vt-tv bindings
+ id: docker_build
+ uses: docker/build-push-action@v3
+ with:
+ push: false
+ tags: ${{ env.DOCKER_TAG }}
+ context: .
+ file: ./ci/build-and-test-bindings.dockerfile
+ outputs: type=local,dest=${{ env.OUTPUT_DIR }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4aa5da7252..355d132182 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,13 +19,17 @@ set(VT_TV_LIBRARY_NS vt::lib::vt-tv)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+# add -fPIC to all targets
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17 CACHE STRING "The C++ standard to use")
endif()
message(STATUS "CMAKE_CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
-option(vt_tv_python_bindings_enabled "Build vt-tv with Python bindings" ON)
-option(vt_tv_openmp_enabled "Build vt-tv with openMP support" ON)
+option(VT_TV_PYTHON_BINDINGS_ENABLED "Build vt-tv with Python bindings" ON)
+option(VT_TV_OPENMP_ENABLED "Build vt-tv with openMP support" ON)
+set(VT_TV_N_THREADS "2" CACHE STRING "Number of OpenMP threads to use")
include(cmake/load_packages.cmake)
@@ -33,20 +37,19 @@ if(APPLE)
add_compile_options(-ffat-lto-objects)
endif()
-if(openmp_enabled)
- set(VT_TV_NUM_THREADS "2" CACHE STRING "Number of threads to use")
- add_definitions(-DVT_TV_NUM_THREADS=${VT_TV_NUM_THREADS})
-endif()
+add_definitions(-DVT_TV_N_THREADS=${VT_TV_N_THREADS})
+add_definitions(-DVT_TV_OPENMP_ENABLED=${VT_TV_OPENMP_ENABLED})
add_custom_target(vt_tv_examples)
add_custom_target(vt_tv_tests)
add_custom_target(vt_tv_apps)
-set(PROJECT_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR})
-set(PROJECT_BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
-set(PROJECT_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
-set(PROJECT_EXAMPLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples)
-set(PROJECT_APP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/apps)
+set(PROJECT_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR})
+set(PROJECT_BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(PROJECT_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
+set(PROJECT_EXAMPLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples)
+set(PROJECT_APP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/apps)
+set(PROJECT_BINDINGS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
include(CTest)
@@ -54,6 +57,7 @@ add_subdirectory(src)
add_subdirectory(examples)
add_subdirectory(tests)
add_subdirectory(apps)
+add_subdirectory(bindings)
configure_file(
cmake/vtTVConfig.cmake.in
@@ -70,34 +74,3 @@ install(
DESTINATION cmake
COMPONENT lib
)
-
-if (vt_tv_python_bindings_enabled)
- # Build the core parts of nanobind once
- nanobind_build_library(nanobind SHARED)
-
- # Compile an extension library
- add_library(my_ext MODULE ${CMAKE_CURRENT_SOURCE_DIR}/circle.cc)
-
- # .. and link it against the nanobind parts
- message(STATUS "vtk libraries: ${VTK_LIBRARIES}")
- target_link_libraries(my_ext PUBLIC ${VTK_LIBRARIES})
- target_link_libraries(my_ext PRIVATE nanobind)
-
- # .. enable size optimizations
- nanobind_opt_size(my_ext)
-
- # .. enable link time optimization
- nanobind_lto(my_ext)
-
- # .. disable the stack protector
- nanobind_disable_stack_protector(my_ext)
-
- # .. set the Python extension suffix
- nanobind_extension(my_ext)
-
- # .. set important compilation flags
- nanobind_compile_options(my_ext)
-
- # .. set important linker flags
- nanobind_link_options(my_ext)
-endif()
diff --git a/README.md b/README.md
index f4efbad5ca..cd8578daf2 100644
--- a/README.md
+++ b/README.md
@@ -16,4 +16,43 @@ using Paraview. Additionally, the task visualizer can produce PNGs
directly using a VTK workflow to render a visualization of ranks and
tasks over phases.
-![Example Output PNG](./docs/example-output-image.png)
\ No newline at end of file
+![Example Output PNG](./docs/example-output-image.png)
+
+## Building the Python bindings
+
+### Requirements
+
+In order to build the python bindings, make sure you have a Python `3.8` or `3.9` environment, with the `nanobind` package installed. You can install `nanobind` with `pip`:
+
+```bash
+pip install nanobind
+```
+
+You must have a C++ compiler that supports C++17, and `cmake` >= 3.17.
+
+Finally, you must have a (C++) [VTK](https://vtk.org/) build available on your system. We recommend building from source, and the currently tested version is `9.3.0`. You can find instructions for building VTK [here](https://gitlab.kitware.com/vtk/vtk/-/blob/master/Documentation/docs/build_instructions/build.md).
+
+### Building
+
+To build the python bindings, you must specify in the `VTTV_VTK_DIR` environment variable the path to the VTK build directory:
+
+```bash
+export VTTV_VTK_DIR=/path/to/vtk/build
+```
+
+
+Then, to install python-environment-wide the binded `vt-tv` python module, run:
+
+```bash
+pip install
+```
+**Optional**
+
+To specify the number of parallel jobs to use during the build, you can set the `VTTV_J` environment variable:
+
+```bash
+export VTTV_J=8
+```
+
+> [!NOTE]
+> Behind the scenes, the usual `cmake` and `make` commands are run. Depending on your system, this can cause the install process to be lengthy as it will be compiling the entire `vt-tv` library.
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index 1c76334fa7..8d7001a55c 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -51,4 +51,10 @@ foreach(APP_FULL ${PROJECT_APPS})
PUBLIC
${VT_TV_LIBRARY_NS}
)
+
+ vtk_module_autoinit(
+ TARGETS ${APP}
+ MODULES ${VTK_LIBRARIES}
+ )
+
endforeach()
diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt
new file mode 100644
index 0000000000..ec615a4126
--- /dev/null
+++ b/bindings/CMakeLists.txt
@@ -0,0 +1,3 @@
+if(VT_TV_PYTHON_BINDINGS_ENABLED)
+ add_subdirectory(python)
+endif()
diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt
new file mode 100644
index 0000000000..13291f9023
--- /dev/null
+++ b/bindings/python/CMakeLists.txt
@@ -0,0 +1,21 @@
+
+# Source files for the Python bindings
+file(
+ GLOB
+ PYTHON_BINDING_SRCS
+ "${CMAKE_CURRENT_SOURCE_DIR}/*.cc"
+)
+
+# Build the core parts of nanobind once
+nanobind_build_library(nanobind SHARED)
+
+# Create the Python bindings for the module
+nanobind_add_module(vttv ${PYTHON_BINDING_SRCS} tv.cc)
+
+# .. Link it to necessary libraries
+target_link_libraries(vttv PUBLIC ${VT_TV_LIBRARY_NS} ${JSON_LIBRARY} ${FMT_LIBRARY})
+
+vtk_module_autoinit(
+ TARGETS vttv
+ MODULES ${VTK_LIBRARIES}
+)
diff --git a/bindings/python/tv.cc b/bindings/python/tv.cc
new file mode 100644
index 0000000000..3558fa631c
--- /dev/null
+++ b/bindings/python/tv.cc
@@ -0,0 +1,140 @@
+#include "tv.h"
+
+namespace vt::tv::bindings::python {
+
+void tvFromJson(const std::vector& input_json_per_rank_list, const std::string& input_yaml_params_str, uint64_t num_ranks) {
+ std::string startup_logo = std::string(" __ __\n")
+ + std::string(" _ __/ /_ / /__ __\n")
+ + std::string("| | / / __/ _____ / __/ | / /\n")
+ + std::string("| |/ / / /____/ / /_ | |/ /\n")
+ + std::string("|___/\\__/ \\__/ |___/\n");
+ fmt::print("==============================\n");
+ fmt::print(startup_logo);
+ fmt::print("==============================\n");
+
+ // parse the input yaml parameters
+ try {
+ // Load the configuration from serialized YAML
+ YAML::Node viz_config = YAML::Load(input_yaml_params_str);
+
+
+ std::array qoi_request = {
+ viz_config["rank_qoi"].as(),
+ "",
+ viz_config["object_qoi"].as()
+ };
+
+ bool save_meshes = viz_config["save_meshes"].as();
+ bool save_pngs = true; // lbaf always saves pngs
+ bool continuous_object_qoi = viz_config["force_continuous_object_qoi"].as();
+
+ std::array grid_size = {
+ viz_config["x_ranks"].as(),
+ viz_config["y_ranks"].as(),
+ viz_config["z_ranks"].as()
+ };
+
+ double object_jitter = viz_config["object_jitter"].as();
+
+ std::string output_dir = viz_config["output_visualization_dir"].as();
+ std::filesystem::path output_path(output_dir);
+
+ // Throw an error if the output directory does not exist or is not absolute
+ if (!std::filesystem::exists(output_path)) {
+ throw std::runtime_error("Visualization output directory does not exist.");
+ }
+ if (!output_path.is_absolute()) {
+ throw std::runtime_error("Visualization output directory must be absolute.");
+ }
+
+ // append / to avoid problems with file stems
+ if (!output_dir.empty() && output_dir.back() != '/') {
+ output_dir += '/';
+ }
+
+ std::string output_file_stem = viz_config["output_visualization_file_stem"].as();
+
+ uint64_t win_size = 2000;
+ if (viz_config["window_size"]) {
+ win_size = viz_config["window_size"].as();
+ }
+
+ // Use automatic font size if not defined by user
+ // 0.025 is the factor of the window size determined to be ideal for the font size
+ uint64_t font_size = 0.025 * win_size;
+ if (viz_config["font_size"]) {
+ font_size = viz_config["font_size"].as();
+ }
+
+ // print all saved configuration parameters
+ fmt::print("Input Configuration Parameters:\n");
+ fmt::print(" x_ranks: {}\n", grid_size[0]);
+ fmt::print(" y_ranks: {}\n", grid_size[1]);
+ fmt::print(" z_ranks: {}\n", grid_size[2]);
+ fmt::print(" object_jitter: {}\n", object_jitter);
+ fmt::print(" rank_qoi: {}\n", qoi_request[0]);
+ fmt::print(" object_qoi: {}\n", qoi_request[2]);
+ fmt::print(" save_meshes: {}\n", save_meshes);
+ fmt::print(" save_pngs: {}\n", save_pngs);
+ fmt::print(" force_continuous_object_qoi: {}\n", continuous_object_qoi);
+ fmt::print(" output_visualization_dir: {}\n", output_dir);
+ fmt::print(" output_visualization_file_stem: {}\n", output_file_stem);
+ fmt::print(" window_size: {}\n", win_size);
+ fmt::print(" font_size: {}\n", font_size);
+
+ using json = nlohmann::json;
+
+ assert(input_json_per_rank_list.size() == num_ranks && "Must have the same number of json files as ranks");
+
+ // Initialize the info object, that will hold data for all ranks for all phases
+ std::unique_ptr info = std::make_unique();
+
+ #ifdef VT_TV_N_THREADS
+ const int threads = VT_TV_N_THREADS;
+ #else
+ const int threads = 2;
+ #endif
+ #ifdef VT_TV_OPENMP_ENABLED
+ #if VT_TV_OPENMP_ENABLED
+ omp_set_num_threads(threads);
+ // print number of threads
+ fmt::print("vt-tv: Using {} threads\n", threads);
+ # pragma omp parallel for
+ #endif
+ #endif
+ for (int64_t rank_id = 0; rank_id < num_ranks; rank_id++) {
+ fmt::print("Reading file for rank {}\n", rank_id);
+ std::string rank_json_str = input_json_per_rank_list[rank_id];
+ utility::JSONReader reader{static_cast(rank_id)};
+ reader.readString(rank_json_str);
+ auto tmpInfo = reader.parse();
+ #ifdef VT_TV_OPENMP_ENABLED
+ #if VT_TV_OPENMP_ENABLED
+ #pragma omp critical
+ #endif
+ #endif
+ {
+ info->addInfo(tmpInfo->getObjectInfo(), tmpInfo->getRank(rank_id));
+ }
+ }
+ // Instantiate render
+ Render render(
+ qoi_request, continuous_object_qoi, *info, grid_size, object_jitter,
+ output_dir, output_file_stem, 1.0, save_meshes, save_pngs, std::numeric_limits::max()
+ );
+ render.generate(font_size, win_size);
+ } catch (std::exception const& e) {
+ std::cout << "vt-tv: Error reading the configuration file: " << e.what() << std::endl;
+ }
+
+ fmt::print("vt-tv: Done.\n");
+}
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+NB_MODULE(vttv, m) {
+ m.def("tvFromJson", &tvFromJson);
+}
+
+} /* end namespace vt::tv::bindings::python */
diff --git a/bindings/python/tv.h b/bindings/python/tv.h
new file mode 100644
index 0000000000..65c3a31312
--- /dev/null
+++ b/bindings/python/tv.h
@@ -0,0 +1,80 @@
+/*
+//@HEADER
+// *****************************************************************************
+//
+// tv.h
+// DARMA/vt-tv => Virtual Transport -- Task Visualizer
+//
+// Copyright 2019 National Technology & Engineering Solutions of Sandia, LLC
+// (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
+// Government retains certain rights in this software.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// * Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Questions? Contact darma@sandia.gov
+//
+// *****************************************************************************
+//@HEADER
+*/
+
+#if !defined INCLUDED_VT_TV_BINDINGS_PYTHON_JSON_INTERFACE_H
+#define INCLUDED_VT_TV_BINDINGS_PYTHON_JSON_INTERFACE_H
+
+#include
+
+#include
+#include "vt-tv/render/render.h"
+#include "vt-tv/api/types.h"
+#include "vt-tv/api/info.h"
+#include "vt-tv/utility/decompression_input_container.h"
+#include "vt-tv/utility/input_iterator.h"
+#include "vt-tv/utility/qoi_serializer.h"
+#include "vt-tv/utility/json_reader.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include