Repository: eWaterCycle/grpc4bmi (language: python)

Issue:
Latest protobuf
Refs #115 

Adds Dockerfiles for heat model in combinations of languages, and versions of bmi, protobuf, grpc4bmi.

To test
1. Build images with `cd test/heat-images && docker compose build`
2. Edit image var in test/heat-images/heat_tester.py and run it


After merge actions
* In test/heat-images/python-bmi02-legacy/Dockerfile, test/heat-images/python-bmi20/Dockerfile, test/heat-images/python-bmi20-pb4/Dockerfile, change `RUN pip install https://github.com/eWaterCycle/grpc4bmi/archive/refs/heads/bmi2.zip` to `RUN pip install grpc4bmi==<new version>`
Apptainer client
As apptainer and singularity are diverging it would be nice that grpc4bmi has first class support for apptainer. Since #111 you can use apptainer in BmiClientSingularity as apptainer creates a singularity symlink, but this make it confusing to tell whether singularity or apptainer is used.

What I would like to do is

```python
from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
client = BmiClientApptainer(image='docker://ewatercycle/marrmot-grpc4bmi:latest')
```

instead of 

```python
from grpc4bmi.bmi_client_singularity import BmiClientSingularity
client = BmiClientSingularity(image='docker://ewatercycle/marrmot-grpc4bmi:latest')
```
Caching bmi client
The gRPC client/server model introduces latency and cost for each method call. However most bmi functions return the same data each time they are called so they could be memoized. It would be nice to have a client which can return a cached value  instead of calling the server. This could be implemented as a new class (`BmiClientCached`) which has a grpc bmi client as constructor argument and uses something like https://docs.python.org/3.8/library/functools.html#functools.lru_cache.

The following methods should not be memoized
* initialize
* update_*
* finalize
* get_current_time
* get_value_*
* set_value_*

The cache should be cleared when `initialize()` is called.


Discussion / hints:

Fix patch:
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..64e336c
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+# Apptainer images
+*.sif
+/.venv
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2d7ce9f..bf589ea 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,39 +5,48 @@ name: CI
 
 on:
   push:
+    branches: [main]
   pull_request:
+    types: [opened, synchronize, reopened]
 
 jobs:
   python:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Set up Python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
-        python-version: 3.6
+        python-version: '3.10'
     - name: Install dependencies
       run: |
-        python -m pip install --upgrade pip
-        pip install -r requirements.txt
-        python setup.py install
-    - name: Setup Singularity
-      uses: eWaterCycle/setup-singularity@v6
+        python -m pip install --upgrade pip wheel
+        pip install -e .[dev,R]
+    - name: Setup Apptainer
+      uses: eWaterCycle/setup-apptainer@v2
       with:
-        singularity-version: 3.6.4
+        apptainer-version: 1.1.3
     - name: Pull Docker image
       run: docker pull ewatercycle/walrus-grpc4bmi:v0.3.1
-    - name: Cache Singularity image
-      id: cache-singularity-image
-      uses: actions/cache@v2
+    - name: Cache Apptainer image
+      id: cache-apptainer-image
+      uses: actions/cache@v3
       with:
-        path: ~/.singularity
-        key: singularity-image
-    - name: Pull Singularity image
-      if: steps.cache-singularity-image.outputs.cache-hit != 'true'
-      run: singularity pull docker://ewatercycle/walrus-grpc4bmi:v0.3.1
+        path: ~/.apptainer
+        key: apptainer-image
+    - name: Pull Apptainer image
+      if: steps.cache-apptainer-image.outputs.cache-hit != 'true'
+      run: apptainer pull docker://ewatercycle/walrus-grpc4bmi:v0.3.1
+    - uses: r-lib/actions/setup-r@v2
+      with:
+        install-r: false
+    - name: Install R dependencies
+      run: |
+        Rscript -e "install.packages('remotes')"
+        Rscript -e "install.packages('R6')"
     - name: Test with pytest
-      run: pytest --cov=grpc4bmi --cov-report xml
+      run: |
+        pytest -vv --cov=grpc4bmi --cov-report xml
     - name: Correct coverage paths
       run: sed -i "s+$PWD/++g" coverage.xml
     - name: SonarCloud analysis
@@ -46,19 +55,33 @@ jobs:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
   cpp:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           submodules: true
       - name: Install grpc
         uses: ewatercycle/setup-grpc@v4
         with:
-          grpc-version: 1.27.2
+          grpc-version: 1.51.1
       - name: Copy grpc to system default
         run: |
           sudo rsync -a $GRPC_ROOT/ /usr/local
           sudo ldconfig
+      - name: Install bmi-cxx
+        run: |
+          git clone -b v2.0 https://github.com/csdms/bmi-cxx.git bmi-cxx
+          mkdir bmi-cxx/build 
+          cd bmi-cxx/build
+          cmake ..
+          make install
+      - name: Install bmi-cxx
+        run: |
+          git clone -b v2.1 https://github.com/csdms/bmi-c.git bmi-c
+          mkdir bmi-c/build 
+          cd bmi-c/build
+          cmake ..
+          make install          
       - name: Build dir
         run: mkdir -p cpp/build
       - name: configure
@@ -70,3 +93,4 @@ jobs:
       - name: ctest
         run: ctest -V
         working-directory: cpp/build
+      # TODO use https://github.com/marketplace/actions/sonarcloud-scan-for-c-and-c
diff --git a/.gitignore b/.gitignore
index 96604a8..0d24000 100644
--- a/.gitignore
+++ b/.gitignore
@@ -100,3 +100,6 @@ ENV/
 
 # mypy
 .mypy_cache/
+
+# Apptainer images
+*.sif
diff --git a/.gitmodules b/.gitmodules
index 40297e5..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +0,0 @@
-[submodule "cpp/bmi-cxx"]
-	path = cpp/bmi-cxx
-	url = https://github.com/csdms/bmi-cxx.git
-[submodule "cpp/bmi-c"]
-	path = cpp/bmi-c
-	url = https://github.com/csdms/bmi-c.git
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..353c93a
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,16 @@
+version: 2
+
+build:
+  os: "ubuntu-22.04"
+  tools:
+    python: "3.10"
+
+sphinx:
+  configuration: docs/conf.py
+
+python:
+  install:
+    - method: pip
+      path: .
+      extra_requirements:
+        - docs
diff --git a/README.md b/README.md
index fb4e793..a2a10b8 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 
 ## Purpose
 
-This software allows you to wrap your Basic Model Interface (BMI) implementation ([https://github.com/csdms/bmi](https://github.com/csdms/bmi)) in a server process and communicate with it via the included Python client. The communication is serialized to protocol buffers by gRPC ([https://grpc.io/](https://grpc.io/)) and occurs over network ports.
+This software allows you to wrap your [Basic Model Interface (BMI)](https://github.com/csdms/bmi) implementation in a server process and communicate with it via the included Python client. The communication is serialized to protocol buffers by [GRPC](https://grpc.io/) and occurs over network ports. Can run models in isolated containers using Docker or Apptainer.
 
 ## Installation
 
@@ -43,13 +43,17 @@ in the cpp folder.
 
 ### Model written in Python
 
-For inspiration look at the example in the test directory. To start a server process that allows calls to your BMI implementation, type
+A model should be a subclass of the `Bmi` class from the [bmipy](https://pypi.org/project/bmipy/2.0/) package.
+
+For inspiration look at the [example](test/fake_models.py) in the test directory. 
+
+To start a server process that allows calls to your BMI implementation, type
 
 ```bash
 run-bmi-server --name <PACKAGE>.<MODULE>.<CLASS> --port <PORT> --path <PATH>
 ```
 
-where ```<PACKAGE>, <MODULE>``` are the Python package and module containing your implementation, ```<CLASS>``` is your
+where ```<PACKAGE>, <MODULE>``` are the python package and module containing your implementation, ```<CLASS>``` is your
 bmi model class name, ```<PORT>``` is any available port on the host system, and optionally ```<PATH>``` denotes an
 additional path that should be added to the system path to make your implementation work. The name option above is
 optional, and if not provided the script will look at the environment variables ```BMI_PACKAGE```, ```BMI_MODULE``` and
@@ -99,9 +103,8 @@ mymodel.initialize(<FILEPATH>)
 ...further BMI calls...
 ```
 
-The package contains also client implementation that own the server process, either as a Python subprocess or a docker
-image or a singularity image running the ```run-bmi-server``` script. For instance
-
+The package contains also client implementation that own the server process, either as a Python subprocess or a Docker
+container or a Singularity container or a Apptainer container running the ```run-bmi-server``` script. For instance
 ```python
 from grpc4bmi.bmi_client_subproc import BmiClientSubProcess
 mymodel = BmiClientSubProcess(<PACKAGE>.<MODULE>.<CLASS>)
@@ -110,18 +113,25 @@ mymodel = BmiClientSubProcess(<PACKAGE>.<MODULE>.<CLASS>)
 will automatically launch the server in a sub-process and
 
 ```python
-from grpc4bmi.bmi_client_subproc import BmiClientDocker
-mymodel = BmiClientDocker(<IMAGE>,<PORT>)
+from grpc4bmi.bmi_client_docker import BmiClientDocker
+mymodel = BmiClientDocker(<IMAGE>, <WORK DIR TO MOUNT>, input_dirs=[<INPUT DIRECTORIES TO MOUNT>])
 ```
-
-will launch a docker container, assuming that a gRPC BMI server will start and exposes the port ```<PORT>```.
+will launch a Docker container based on supplied Docker image
+and will mount supplied directories to share files between the container and host.
 
 ```python
 from grpc4bmi.bmi_client_singularity import BmiClientSingularity
-mymodel = BmiClientSingularity(<IMAGE>,<PORT>)
+mymodel = BmiClientSingularity(<IMAGE>, <WORK DIR TO MOUNT>, input_dirs=[<INPUT DIRECTORIES TO MOUNT>])
 ```
+will launch a singularity container on based supplied Singularity image
+and will mount supplied directories to share files between the container and host.
 
-will launch a singularity container, assuming that a gRPC BMI server will start and exposes the port ```<PORT>```.
+```python
+from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+mymodel = BmiClientApptainer(<IMAGE>, <WORK DIR TO MOUNT>, input_dirs=[<INPUT DIRECTORIES TO MOUNT>])
+```
+will launch a Apptainer container on based supplied Apptainer image
+and will mount supplied directories to share files between the container and host.
 
 For more documentation see [https://grpc4bmi.readthedocs.io/](https://grpc4bmi.readthedocs.io/).
 
@@ -130,10 +140,16 @@ For more documentation see [https://grpc4bmi.readthedocs.io/](https://grpc4bmi.r
 When developers change the proto-file, it is necessary to install gRPC tools Python packages in your Python environment:
 
 ```bash
-pip install -r requirements.txt
-pip install -e .
+# Create virtual env
+python3 -m venv .venv
+. venv/bin/activate
+# Make sure latest pip and wheel are install
+pip install -U pip wheel
+pip install -e .[dev]
 # For R integration also install the R extras with
 pip install -e .[R]
+# For building docs (cd docs && make html) also install the docs extras with
+pip install -e .[docs]
 ```
 
 and install the C++ runtime and `protoc` command as described in <https://github.com/google/protobuf/blob/master/src/README.md>.
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 327cac3..b719eed 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -1,17 +1,25 @@
-cmake_minimum_required (VERSION 2.6)
+cmake_minimum_required (VERSION 3.6)
 
-project (grpc4bmi)
+project (grpc4bmi C CXX)
 set (GRPC4BMI_VERSION 0.2)
 
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/grpc4bmi.pc.cmake
                ${CMAKE_CURRENT_BINARY_DIR}/grpc4bmi.pc)
 
-add_compile_options(-std=c++11)
+if (NOT DEFINED CMAKE_CXX_STANDARD)
+  set (CMAKE_CXX_STANDARD 14)
+endif()
 
+find_package(Threads REQUIRED)
 find_package(Protobuf REQUIRED)
+message(STATUS "Using protobuf ${Protobuf_VERSION}")
 find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin REQUIRED)
-find_library(GRPCPP_UNSECURE grpc++_unsecure)
-find_library(GRPCLIB grpc)
+find_library(GRPCREFLECTION grpc++_reflection)
+find_library(GRPCLIB grpc CONFIG REQUIRED)
+message(STATUS "Using gRPC ${GRPCLIB_VERSION}")
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(BMI REQUIRED IMPORTED_TARGET bmic bmicxx)
+message(STATUS "Using BMI ${BMI_VERSION}")
 
 set(PROTO_PATH "${CMAKE_SOURCE_DIR}/../proto/grpc4bmi")
 add_custom_command(
@@ -31,11 +39,20 @@ add_custom_command(
     )
 set(GRPC_SRC_FILES ${CMAKE_CURRENT_BINARY_DIR}/bmi.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/bmi.grpc.pb.cc)
 set(GRPC_HDR_FILES ${CMAKE_CURRENT_BINARY_DIR}/bmi.pb.h ${CMAKE_CURRENT_BINARY_DIR}/bmi.grpc.pb.h)
-link_libraries(${PROTOBUF_LIBRARY} ${GRPCPP_UNSECURE} ${GRPC} ${BMIC})
+
 include_directories(${CMAKE_SOURCE_DIR})
 include_directories(${CMAKE_CURRENT_BINARY_DIR})
-add_library (grpc4bmi SHARED bmi-c/bmi.h bmi-cxx/bmi.hxx bmi_cpp_extension.h bmi_cpp_extension.cc
+include_directories(${BMI_INCLUDE_DIRS})
+
+add_library (grpc4bmi SHARED bmi_cpp_extension.h bmi_cpp_extension.cc
     bmi_c_wrapper.h bmi_c_wrapper.cc bmi_grpc_server.cc bmi_grpc_server.h ${GRPC_SRC_FILES} ${GRPC_HDR_FILES})
+
+target_link_libraries(
+    grpc4bmi
+    ${GRPCREFLECTION}
+    ${GRPC}
+    ${PROTOBUF_LIBRARY}
+)
 set_target_properties(grpc4bmi PROPERTIES PUBLIC_HEADER "bmi_cpp_extension.h;bmi_c_wrapper.h;bmi_grpc_server.h;${GRPC_HDR_FILES}")
 add_subdirectory (test)
 
diff --git a/cpp/bmi-c b/cpp/bmi-c
deleted file mode 160000
index 9b5b34d..0000000
--- a/cpp/bmi-c
+++ /dev/null
@@ -1,1 +0,0 @@
-Subproject commit 9b5b34d7c43a7d8732dc2b245b102bcb505946cf
diff --git a/cpp/bmi-cxx b/cpp/bmi-cxx
deleted file mode 160000
index 46cec3d..0000000
--- a/cpp/bmi-cxx
+++ /dev/null
@@ -1,1 +0,0 @@
-Subproject commit 46cec3de7ce624d9f1f4cffcf00bcf64d27b8d17
diff --git a/cpp/bmi_c_wrapper.cc b/cpp/bmi_c_wrapper.cc
index 7d6bcc0..442bef2 100644
--- a/cpp/bmi_c_wrapper.cc
+++ b/cpp/bmi_c_wrapper.cc
@@ -15,254 +15,280 @@ void checkStatus(int status)
 
 void BmiCWrapper::Initialize(std::string configfile)
 {
-    checkStatus(this->model->initialize(this->model->self, const_cast<char*>(configfile.c_str())));
+    checkStatus(this->model->initialize(this->model, const_cast<char*>(configfile.c_str())));
 }
 
 void BmiCWrapper::Update()
 {
     double curtime, timestep;
-    checkStatus(this->model->get_current_time(this->model->self, &curtime));
-    checkStatus(this->model->get_time_step(this->model->self, &timestep));
-    checkStatus(this->model->update(this->model->self));
+    checkStatus(this->model->get_current_time(this->model, &curtime));
+    checkStatus(this->model->get_time_step(this->model, &timestep));
+    checkStatus(this->model->update(this->model));
 }
 
 void BmiCWrapper::UpdateUntil(double time)
 {
-    checkStatus(this->model->update_until(this->model->self, time));
+    checkStatus(this->model->update_until(this->model, time));
 }
 
 void BmiCWrapper::Finalize()
 {
-    checkStatus(this->model->finalize(this->model->self));
+    checkStatus(this->model->finalize(this->model));
 }
 
 std::string BmiCWrapper::GetComponentName()
 {
-    char* dest;
-    checkStatus(this->model->get_component_name(this->model->self, dest));
+    char dest[BMI_MAX_COMPONENT_NAME];
+    checkStatus(this->model->get_component_name(this->model, dest));
     return dest;
 }
 
 int BmiCWrapper::GetInputItemCount()
 {
     int dest;
-    checkStatus(this->model->get_input_item_count(this->model->self, &dest));
+    checkStatus(this->model->get_input_item_count(this->model, &dest));
     return dest;
 }
 
 int BmiCWrapper::GetOutputItemCount()
 {
     int dest;
-    checkStatus(this->model->get_output_item_count(this->model->self, &dest));
+    checkStatus(this->model->get_output_item_count(this->model, &dest));
     return dest;
 }
 
 std::vector<std::string> BmiCWrapper::GetInputVarNames()
 {
-    char** dest;
-    checkStatus(this->model->get_input_var_names(this->model->self, dest));
-    // TODO convert dest to vector of strings
+    size_t count = static_cast<size_t>(GetInputItemCount());
+    // TODO replace with std::vector<std::unique_ptr> oid???
+    // Current impl is not safe against exceptions
+    char** dest = reinterpret_cast<char **>(malloc(sizeof(char *) * count));
+    for (size_t i = 0; i < count; ++i) {
+        dest[i] = reinterpret_cast<char *>(malloc(BMI_MAX_VAR_NAME + 1));
+    }
+    checkStatus(this->model->get_input_var_names(this->model, dest));
+
+    std::vector<std::string> result;
+    for (size_t i = 0; i < count; ++i) {
+        result.emplace_back(dest[i]);
+        free(dest[i]);
+    }
+    free(dest);
+    return result;
 }
 
 std::vector<std::string> BmiCWrapper::GetOutputVarNames()
 {
-    char** dest;
-    checkStatus(this->model->get_output_var_names(this->model->self, dest));
-    // TODO convert dest to vector of strings
+    size_t count = static_cast<size_t>(GetOutputItemCount());
+    // TODO replace with std::vector<std::unique_ptr> oid???
+    // Current impl is not safe against exceptions
+    char** dest = reinterpret_cast<char **>(malloc(sizeof(char *) * count));
+    for (size_t i = 0; i < count; ++i) {
+        dest[i] = reinterpret_cast<char *>(malloc(BMI_MAX_VAR_NAME + 1));
+    }
+    checkStatus(this->model->get_output_var_names(this->model, dest));
+
+    std::vector<std::string> result;
+    for (size_t i = 0; i < count; ++i) {
+        result.emplace_back(dest[i]);
+        free(dest[i]);
+    }
+    free(dest);
+    return result;
 }
 
 int BmiCWrapper::GetVarGrid(std::string name)
 {
     int dest;
-    checkStatus(this->model->get_var_grid(this->model->self, const_cast<char*>(name.c_str()), &dest));
+    checkStatus(this->model->get_var_grid(this->model, const_cast<char*>(name.c_str()), &dest));
     return dest;
 }
 
 std::string BmiCWrapper::GetVarType(std::string name)
 {
-    char* vtype;
-    checkStatus(this->model->get_var_type(this->model->self, const_cast<char*>(name.c_str()), vtype));
+    char vtype[BMI_MAX_TYPE_NAME];
+    checkStatus(this->model->get_var_type(this->model, const_cast<char*>(name.c_str()), vtype));
     return vtype;
 }
 
 int BmiCWrapper::GetVarItemsize(std::string name)
 {
     int dest;
-    checkStatus(this->model->get_var_itemsize(this->model->self, const_cast<char*>(name.c_str()), &dest));
+    checkStatus(this->model->get_var_itemsize(this->model, const_cast<char*>(name.c_str()), &dest));
     return dest;
 }
 
 std::string BmiCWrapper::GetVarUnits(std::string name)
 {
-    char* dest;
-    checkStatus(this->model->get_var_units(this->model->self, const_cast<char*>(name.c_str()), dest));
+    char dest[BMI_MAX_VAR_NAME];
+    checkStatus(this->model->get_var_units(this->model, const_cast<char*>(name.c_str()), dest));
     return dest;
 }
 
 int BmiCWrapper::GetVarNbytes(std::string name)
 {
     int dest;
-    checkStatus(this->model->get_var_nbytes(this->model->self, const_cast<char*>(name.c_str()), &dest));
+    checkStatus(this->model->get_var_nbytes(this->model, const_cast<char*>(name.c_str()), &dest));
     return dest;
 }
 
 std::string BmiCWrapper::GetVarLocation(std::string name)
 {
-    char* location;
-    checkStatus(this->model->get_var_location(this->model->self, const_cast<char*>(name.c_str()), location));
+    char location[BMI_MAX_UNITS_NAME];
+    checkStatus(this->model->get_var_location(this->model, const_cast<char*>(name.c_str()), location));
     return location;
 }
 
 double BmiCWrapper::GetCurrentTime()
 {
     double dest;
-    checkStatus(this->model->get_current_time(this->model->self, &dest));
+    checkStatus(this->model->get_current_time(this->model, &dest));
     return dest;
 }
 
 double BmiCWrapper::GetStartTime()
 {
     double dest;
-    checkStatus(this->model->get_start_time(this->model->self, &dest));
+    checkStatus(this->model->get_start_time(this->model, &dest));
     return dest;
 }
 
 double BmiCWrapper::GetEndTime()
 {
     double dest;
-    checkStatus(this->model->get_end_time(this->model->self, &dest));
+    checkStatus(this->model->get_end_time(this->model, &dest));
     return dest;
 }
 
 std::string BmiCWrapper::GetTimeUnits()
 {
-    char* dest;
-    checkStatus(this->model->get_time_units(this->model->self, dest));
+    char dest[BMI_MAX_UNITS_NAME];
+    checkStatus(this->model->get_time_units(this->model, dest));
     return dest;
 }
 
 double BmiCWrapper::GetTimeStep()
 {
     double dest;
-    checkStatus(this->model->get_time_step(this->model->self, &dest));
+    checkStatus(this->model->get_time_step(this->model, &dest));
     return dest;
 }
 
 void BmiCWrapper::GetValue(std::string name, void* dest)
 {
-    checkStatus(this->model->get_value(this->model->self, const_cast<char*>(name.c_str()), dest));
+    checkStatus(this->model->get_value(this->model, const_cast<char*>(name.c_str()), dest));
 }
 
 void* BmiCWrapper::GetValuePtr(std::string name)
 {
     void* dest;
-    checkStatus(this->model->get_value_ptr(this->model->self, const_cast<char*>(name.c_str()), &dest));
+    checkStatus(this->model->get_value_ptr(this->model, const_cast<char*>(name.c_str()), &dest));
     return dest;
 }
 
 void BmiCWrapper::GetValueAtIndices(std::string name, void* dest, int* pts, int numpts)
 {
-    checkStatus(this->model->get_value_at_indices(this->model->self, const_cast<char*>(name.c_str()), dest, const_cast<int*>(pts), numpts));
+    checkStatus(this->model->get_value_at_indices(this->model, const_cast<char*>(name.c_str()), dest, const_cast<int*>(pts), numpts));
 }
 
 void BmiCWrapper::SetValue(std::string name, void* src)
 {
-    checkStatus(this->model->set_value(this->model->self, const_cast<char*>(name.c_str()), src));
+    checkStatus(this->model->set_value(this->model, const_cast<char*>(name.c_str()), src));
 }
 
 void BmiCWrapper::SetValueAtIndices(std::string name, int* pts, int numpts, void* values)
 {
-    checkStatus(this->model->set_value_at_indices(this->model->self, const_cast<char*>(name.c_str()), pts, numpts, values));
+    checkStatus(this->model->set_value_at_indices(this->model, const_cast<char*>(name.c_str()), pts, numpts, values));
 }
 
 int BmiCWrapper::GetGridSize(int id)
 {
     int dest;
-    checkStatus(this->model->get_grid_size(this->model->self, id, &dest));
+    checkStatus(this->model->get_grid_size(this->model, id, &dest));
     return dest;
 }
 
 int BmiCWrapper::GetGridRank(int id)
 {
     int dest;
-    checkStatus(this->model->get_grid_size(this->model->self, id, &dest));
+    checkStatus(this->model->get_grid_rank(this->model, id, &dest));
     return dest;
 }
 
 std::string BmiCWrapper::GetGridType(int id)
 {
-    char* dest;
-    checkStatus(this->model->get_grid_type(this->model->self, id, dest));
+    char dest[BMI_MAX_TYPE_NAME];
+    checkStatus(this->model->get_grid_type(this->model, id, dest));
     return dest;
 }
 
 void BmiCWrapper::GetGridShape(int id, int* dest)
 {
-    checkStatus(this->model->get_grid_shape(this->model->self, id, dest));
+    checkStatus(this->model->get_grid_shape(this->model, id, dest));
 }
 
 void BmiCWrapper::GetGridSpacing(int id, double* dest)
 {
-    checkStatus(this->model->get_grid_spacing(this->model->self, id, dest));
+    checkStatus(this->model->get_grid_spacing(this->model, id, dest));
 }
 
 void BmiCWrapper::GetGridOrigin(int id, double* dest)
 {
-    checkStatus(this->model->get_grid_origin(this->model->self, id, dest));
+    checkStatus(this->model->get_grid_origin(this->model, id, dest));
 }
 
 void BmiCWrapper::GetGridX(int id, double* dest)
 {
-    checkStatus(this->model->get_grid_x(this->model->self, id, dest));
+    checkStatus(this->model->get_grid_x(this->model, id, dest));
 }
 
 void BmiCWrapper::GetGridY(int id, double* dest)
 {
-    checkStatus(this->model->get_grid_y(this->model->self, id, dest));
+    checkStatus(this->model->get_grid_y(this->model, id, dest));
 }
 
 void BmiCWrapper::GetGridZ(int id, double* dest)
 {
-    checkStatus(this->model->get_grid_z(this->model->self, id, dest));
+    checkStatus(this->model->get_grid_z(this->model, id, dest));
 }
 
 int BmiCWrapper::GetGridNodeCount(int grid)
 {
     int dest;
-    checkStatus(this->model->get_grid_node_count(this->model->self, grid, &dest));
+    checkStatus(this->model->get_grid_node_count(this->model, grid, &dest));
     return dest;
 }
 
 int BmiCWrapper::GetGridEdgeCount(int grid)
 {
     int dest;
-    checkStatus(this->model->get_grid_edge_count(this->model->self, grid, &dest));
+    checkStatus(this->model->get_grid_edge_count(this->model, grid, &dest));
     return dest;
 }
 
 int BmiCWrapper::GetGridFaceCount(int grid)
 {
     int dest;
-    checkStatus(this->model->get_grid_face_count(this->model->self, grid, &dest));
+    checkStatus(this->model->get_grid_face_count(this->model, grid, &dest));
     return dest;
 }
 
 void BmiCWrapper::GetGridEdgeNodes(int grid, int *edge_nodes)
 {
-    checkStatus(this->model->get_grid_edge_nodes(this->model->self, grid, edge_nodes));
+    checkStatus(this->model->get_grid_edge_nodes(this->model, grid, edge_nodes));
 }
 
 void BmiCWrapper::GetGridFaceEdges(int grid, int *face_edges)
 {
-    checkStatus(this->model->get_grid_edge_nodes(this->model->self, grid, face_edges));
+    checkStatus(this->model->get_grid_edge_nodes(this->model, grid, face_edges));
 }
 
 void BmiCWrapper::GetGridFaceNodes(int grid, int *face_nodes)
 {
-    checkStatus(this->model->get_grid_face_nodes(this->model->self, grid, face_nodes));
+    checkStatus(this->model->get_grid_face_nodes(this->model, grid, face_nodes));
 }
 
 void BmiCWrapper::GetGridNodesPerFace(int grid, int *nodes_per_face)
 {
-    checkStatus(this->model->get_grid_nodes_per_face(this->model->self, grid, nodes_per_face));
+    checkStatus(this->model->get_grid_nodes_per_face(this->model, grid, nodes_per_face));
 }
diff --git a/cpp/bmi_c_wrapper.h b/cpp/bmi_c_wrapper.h
index 31cc5e4..86e608e 100644
--- a/cpp/bmi_c_wrapper.h
+++ b/cpp/bmi_c_wrapper.h
@@ -6,10 +6,10 @@
 
 #ifndef BMI_INCLUDED
 #define BMI_INCLUDED
-#include "bmi-c/bmi.h"
+#include "bmi.h"
 #undef BMI_SUCCESS
 #undef BMI_FAILURE
-#include "bmi-cxx/bmi.hxx"
+#include "bmi.hxx"
 #endif
   
 typedef Bmi BMIModel;
diff --git a/cpp/bmi_cpp_extension.h b/cpp/bmi_cpp_extension.h
index 9b0e26c..922b718 100644
--- a/cpp/bmi_cpp_extension.h
+++ b/cpp/bmi_cpp_extension.h
@@ -7,7 +7,7 @@
 
 #ifndef BMI_INCLUDED
 #define BMI_INCLUDED
-#include "bmi-cxx/bmi.hxx"
+#include "bmi.hxx"
 #endif
 
 class BmiCppExtension: public bmi::Bmi 
diff --git a/cpp/bmi_grpc_server.cc b/cpp/bmi_grpc_server.cc
index d267f09..4d8afbd 100644
--- a/cpp/bmi_grpc_server.cc
+++ b/cpp/bmi_grpc_server.cc
@@ -1,8 +1,10 @@
-#include <grpc/grpc.h>
+#include <grpcpp/grpcpp.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
 #include <grpcpp/server_context.h>
 #include <grpcpp/security/server_credentials.h>
+#include <grpcpp/ext/proto_server_reflection_plugin.h>
+#include <grpcpp/health_check_service_interface.h>
 #include "bmi_grpc_server.h"
 #include "bmi_c_wrapper.h"
 
@@ -815,15 +817,17 @@ void run_bmi_server(BmiClass *model, int argc, char *argv[])
     {
         server_address = "0.0.0.0:" + std::string(argv[1]);
     }
-    std::string bmi_port = std::getenv("BMI_PORT");
-    if(!bmi_port.empty()) {
-        server_address = "0.0.0.0:" + bmi_port;
+    if(const char* bmi_port = std::getenv("BMI_PORT")) {
+        server_address = "0.0.0.0:" + std::string(bmi_port);
     }
-    std::cerr << "BMI grpc server attached to server address " << server_address << std::endl;
     BmiGRPCService service(model);
+    grpc::EnableDefaultHealthCheckService(true);
+    grpc::reflection::InitProtoReflectionServerBuilderPlugin();
     grpc::ServerBuilder builder;
     builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
     builder.RegisterService(&service);
     std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+    std::cerr << "BMI grpc server attached to server address " << server_address << std::endl;
     server->Wait();
+    std::cerr << "Waiting";
 }
diff --git a/cpp/bmi_grpc_server.h b/cpp/bmi_grpc_server.h
index 4b517d3..ec2792f 100644
--- a/cpp/bmi_grpc_server.h
+++ b/cpp/bmi_grpc_server.h
@@ -2,10 +2,10 @@
 #define BMI_GRPC_SERVER_H_INCLUDED
 
 #include "bmi.grpc.pb.h"
-#include "bmi-c/bmi.h"
+#include "bmi.h"
 #undef BMI_SUCCESS
 #undef BMI_FAILURE
-#include "bmi-cxx/bmi.hxx"
+#include "bmi.hxx"
 
 typedef bmi::Bmi BmiClass;
 typedef Bmi BmiModel;
diff --git a/docs/_static/design-overview.svg b/docs/_static/design-overview.svg
index 0d4d06b..d10ea17 100644
--- a/docs/_static/design-overview.svg
+++ b/docs/_static/design-overview.svg
@@ -1,2 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1114px" height="313px" viewBox="-0.5 -0.5 1114 313" content="&lt;mxfile modified=&quot;2019-03-29T15:45:30.414Z&quot; host=&quot;www.draw.io&quot; agent=&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/8.8.0 Chrome/61.0.3163.100 Electron/2.0.2 Safari/537.36&quot; etag=&quot;V1VY8AJ6NVtIS9ibkReL&quot; version=&quot;10.5.9&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;g6TwU9qutF0pdJ5EpEK3&quot; name=&quot;Page-1&quot;&gt;5VrbcpswEP0az6QPzYC42DzGJGk704vHnl6mbwoooEZGrpBv/fpKIDAgnJDW2JPYL4iVWFnnaFd7sAeWP9+8Y3ARf6IhIgNghJuBdT0AwDRdIC7Sss0tQ8PODRHDoRq0M8zwH6SMhrIucYjS2kBOKeF4UTcGNElQwGs2yBhd14fdU1KfdQEjpBlmASS69TsOeaysrmPvOt4jHMW8XLGX98xhMVotJY1hSNcVk3UzsHxGKc9b842PiESvACZ/7nZPb/nNGEp4lwem38Y/vIV//TNY//qJPqZ/3OmPt2oZK0iWasWTLY9pImwX6DK6FNfPlKM7Sh/eqGXwbQEOo8skRNK9MbDG6xhzNFvAQPauxXYQtpjPibgzRXOFGMcC2CuCo0TYOJUDUs7oQwmtwGSsvpAYjTZ7V2qW+Imdh+gccbYVQ9QDwFIcqE1nFxSsKxSOlC2usuep7QrVtolK3ztgRUNh+wycHQ3nAXCJmHYc4pVoRrI5/vShsIpJKh1PIF9BuY2Ee0yITwll2bPWffaRdprwWzjHRML0cRngEIpZfJqkVM5zECbsOhOOCXQm3BYmLKMnIkYtRDTARUl4JVOHuAsITFMc1DHuhhsKtczyJGoVVJwWUAobQwRyvKq7b0NKzTChWExckmLbRp2UYQPslC5ZgNRT1YzScDQCTzjikEWIa44y4spl/zuXxSFRITOaTnyZsxBfU/YgWhNGOQ0oKQLrjhVBBWRym6LfS5RybQ+ILc/rrOeZqoijhCaoEVrKBFV+CwTZiLUkvjkOw2yftMVqPbCPEKGO0+DQasmV3jEj1DQ1Vq9p8CCwBLcznERLAhnm2xwIDnEie45xMoUwjTOf8qFIZgbl/wA0jJox2UJDCXmVBmD0xgPYF10zxFYSdOMiLxb+szKoI22e9sQaDRtEFBVsNR6GLUTYTl88WBoPWaFwDui7AFw63fDvLx/pNXKhc3xx3RExADI4pq+aD3fYlY/+4qFTLf01zTJUztCrrqltp2NN7fQWIe6+k8InGGUVY8dc9XJQd06P+rAlDs5MymgKZGTXXXSVMpomajrqW8routTK9cmCbDVaz1edeHbb+dOmT0BfcQf0ekwjKBJILZqSQd5MIBe4J5kFGNIalCImH7UXNfWKE94VkxjPRdM0m+EChhqYx1YZnobma1B7j+ycR3PlkdEv9sNjGk/ciTWPX1ZZ+yz8N3UFoYLDOrXwA/qLECX8zpUS7/RqsEj2j6hBxU4uB/38cksZZ/CFafUDMGZapxeMoE0wnlmhbDVOfrcZIJ3f+ZsNR17D0Z5CWaALt5VhCzkgfcYX9mq/bopG7vGgVTjQhawpq/CZWBSvHIrNnxLsfJCoJPaP0bbc+RTxzS3jjPSM0Lb3+yvhden8ZfpCCZL96n8TwD4MYaYxtOuMgTbZZR+GMnG7+xNEHsm7/5JYN38B&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);"><defs/><g><rect x="1" y="1" width="180" height="192" fill="#ffffff" stroke="#000000" stroke-width="2" pointer-events="none"/><g transform="translate(28.5,8.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="124" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 124px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Python (e.g. Notebook)</div></div></foreignObject><text x="62" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Python (e.g. Notebook)</text></switch></g><rect x="11" y="93" width="160" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(79.5,101.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="22" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 24px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><div>BMI</div></div></div></foreignObject><text x="11" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">[Not supported by viewer]</text></switch></g><path d="M 202 151 L 575.63 151" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 580.88 151 L 573.88 154.5 L 575.63 151 L 573.88 147.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(347.5,112.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="118" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 118px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">gRPC Network Protocol<br />2. Request</div></div></foreignObject><text x="59" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">gRPC Network Protocol&lt;br&gt;2. Request</text></switch></g><rect x="602" y="111" width="230" height="200" fill="#ffffff" stroke="#000000" stroke-width="2" stroke-dasharray="6 6" pointer-events="none"/><g transform="translate(640.5,118.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="152" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 154px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Docker/Singularity Container</div></div></foreignObject><text x="76" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Docker/Singularity Container</text></switch></g><rect x="632" y="143" width="170" height="45" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(662.5,159.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="108" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 109px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">gRPC Server (Python)</div></div></foreignObject><text x="54" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">gRPC Server (Python)</text></switch></g><rect x="632" y="203.5" width="170" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(682.5,212.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="68" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 68px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">BMI (Python)</div></div></foreignObject><text x="34" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">BMI (Python)</text></switch></g><rect x="632" y="248.5" width="170" height="45" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(657.5,264.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="118" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 120px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Model Code (Python, R)</div></div></foreignObject><text x="59" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">Model Code (Python, R)</text></switch></g><rect x="11" y="33" width="160" height="50" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(65.5,51.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="51" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 52px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><div>User Code</div></div></div></foreignObject><text x="26" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">&lt;div&gt;User Code&lt;/div&gt;</text></switch></g><rect x="11" y="133" width="160" height="50" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(37.5,151.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="106" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 108px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">gRPC Client (Python)</div></div></foreignObject><text x="53" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">gRPC Client (Python)</text></switch></g><path d="M 582 165 L 208.37 165" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 203.12 165 L 210.12 161.5 L 208.37 165 L 210.12 168.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(386.5,179.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">3. Reply</div></div></foreignObject><text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">3. Reply</text></switch></g><rect x="882" y="108" width="230" height="200" fill="#ffffff" stroke="#000000" stroke-width="2" stroke-dasharray="6 6" pointer-events="none"/><g transform="translate(920.5,115.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="152" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 154px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Docker/Singularity Container</div></div></foreignObject><text x="76" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Docker/Singularity Container</text></switch></g><rect x="912" y="140" width="170" height="45" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(948.5,156.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="96" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 96px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">gRPC Server (C++)</div></div></foreignObject><text x="48" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">gRPC Server (C++)</text></switch></g><rect x="912" y="200.5" width="170" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(968.5,209.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="56" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 56px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">BMI (C++)</div></div></foreignObject><text x="28" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">BMI (C++)</text></switch></g><rect x="912" y="245.5" width="170" height="45" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(922.5,261.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="148" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 148px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Model Code (C++, C, Fortran)</div></div></foreignObject><text x="74" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">Model Code (C++, C, Fortran)</text></switch></g><path d="M 82 211 L 82 261 Q 82 271 92 271 L 565.63 271" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 570.88 271 L 563.88 274.5 L 565.63 271 L 563.88 267.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(312.5,235.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="68" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 70px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">1. Start Server<br />4. Stop Server<br /></div></div></foreignObject><text x="34" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Lucida Console">[Not supported by viewer]</text></switch></g><g transform="translate(838.5,202.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="34" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 24px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 34px; white-space: nowrap; word-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">OR</div></div></foreignObject><text x="17" y="25" fill="#000000" text-anchor="middle" font-size="24px" font-family="Lucida Console">OR</text></switch></g></g></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1113px" height="312px" viewBox="-0.5 -0.5 1113 312"><defs/><g><rect x="1" y="1" width="180" height="192" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 8px; margin-left: 2px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Python (e.g. Notebook)</div></div></div></foreignObject><text x="91" y="20" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Python (e.g. Notebook)</text></switch></g><rect x="11" y="93" width="160" height="30" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 108px; margin-left: 12px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>BMI</div></div></div></div></foreignObject><text x="91" y="112" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">BMI</text></switch></g><path d="M 202 151 L 575.63 151" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 580.88 151 L 573.88 154.5 L 575.63 151 L 573.88 147.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="312" y="111" width="190" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 188px; height: 1px; padding-top: 126px; margin-left: 313px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">gRPC Network Protocol<br />2. Request</div></div></div></foreignObject><text x="407" y="130" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">gRPC Network Protocol...</text></switch></g><rect x="602" y="111" width="230" height="200" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-dasharray="6 6" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 228px; height: 1px; padding-top: 118px; margin-left: 603px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Docker/Apptainer Container</div></div></div></foreignObject><text x="717" y="130" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Docker/Apptainer Container</text></switch></g><rect x="632" y="143" width="170" height="45" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 166px; margin-left: 633px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">gRPC Server (Python)</div></div></div></foreignObject><text x="717" y="169" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">gRPC Server (Python)</text></switch></g><rect x="632" y="203.5" width="170" height="30" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 219px; margin-left: 633px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">BMI (Python)</div></div></div></foreignObject><text x="717" y="222" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">BMI (Python)</text></switch></g><rect x="632" y="248.5" width="170" height="45" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 271px; margin-left: 633px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Model Code (Python, R)</div></div></div></foreignObject><text x="717" y="275" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">Model Code (Python, R)</text></switch></g><rect x="11" y="33" width="160" height="50" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 58px; margin-left: 12px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>User Code</div></div></div></div></foreignObject><text x="91" y="62" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">User Code</text></switch></g><rect x="11" y="133" width="160" height="50" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 158px; margin-left: 12px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">gRPC Client (Python)</div></div></div></foreignObject><text x="91" y="162" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">gRPC Client (Python)</text></switch></g><path d="M 582 165 L 208.37 165" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 203.12 165 L 210.12 161.5 L 208.37 165 L 210.12 168.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="312" y="175.5" width="190" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 188px; height: 1px; padding-top: 186px; margin-left: 313px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">3. Reply</div></div></div></foreignObject><text x="407" y="189" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">3. Reply</text></switch></g><rect x="882" y="108" width="230" height="200" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-dasharray="6 6" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 228px; height: 1px; padding-top: 115px; margin-left: 883px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">Docker/Apptainer Container</div></div></div></foreignObject><text x="997" y="127" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Docker/Apptainer Container</text></switch></g><rect x="912" y="140" width="170" height="45" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 163px; margin-left: 913px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">gRPC Server (C++)</div></div></div></foreignObject><text x="997" y="166" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">gRPC Server (C++)</text></switch></g><rect x="912" y="200.5" width="170" height="30" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 216px; margin-left: 913px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">BMI (C++)</div></div></div></foreignObject><text x="997" y="219" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">BMI (C++)</text></switch></g><rect x="912" y="245.5" width="170" height="45" fill="#ffffff" stroke="rgb(0, 0, 0)" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 268px; margin-left: 913px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">Model Code (C++, C, Fortran)</div></div></div></foreignObject><text x="997" y="272" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">Model Code (C++, C, Fortran)</text></switch></g><path d="M 82 211 L 82 261 Q 82 271 92 271 L 565.63 271" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/><path d="M 570.88 271 L 563.88 274.5 L 565.63 271 L 563.88 267.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 249px; margin-left: 273px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">1. Start Server<br />4. Stop Server<br /></div></div></div></foreignObject><text x="347" y="253" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="12px" text-anchor="middle">1. Start Server...</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 216px; margin-left: 837px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 24px; font-family: &quot;Lucida Console&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">OR</div></div></div></foreignObject><text x="856" y="223" fill="rgb(0, 0, 0)" font-family="Lucida Console" font-size="24px" text-anchor="middle">OR</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
\ No newline at end of file
diff --git a/docs/_static/design-overview.xml b/docs/_static/design-overview.xml
old mode 100644
new mode 100755
index 0b20df6..45c5d16
--- a/docs/_static/design-overview.xml
+++ b/docs/_static/design-overview.xml
@@ -1,1 +1,1 @@
-<mxfile modified="2019-03-29T15:45:23.435Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/8.8.0 Chrome/61.0.3163.100 Electron/2.0.2 Safari/537.36" etag="E8JU1hGCv8wWEk0ACa1m" version="10.5.9" type="device"><diagram id="g6TwU9qutF0pdJ5EpEK3" name="Page-1">5VrbcpswEP0az6QPzYC42DzGJGk704vHnl6mbwoooEZGrpBv/fpKIDAgnJDW2JPYL4iVWFnnaFd7sAeWP9+8Y3ARf6IhIgNghJuBdT0AwDRdIC7Sss0tQ8PODRHDoRq0M8zwH6SMhrIucYjS2kBOKeF4UTcGNElQwGs2yBhd14fdU1KfdQEjpBlmASS69TsOeaysrmPvOt4jHMW8XLGX98xhMVotJY1hSNcVk3UzsHxGKc9b842PiESvACZ/7nZPb/nNGEp4lwem38Y/vIV//TNY//qJPqZ/3OmPt2oZK0iWasWTLY9pImwX6DK6FNfPlKM7Sh/eqGXwbQEOo8skRNK9MbDG6xhzNFvAQPauxXYQtpjPibgzRXOFGMcC2CuCo0TYOJUDUs7oQwmtwGSsvpAYjTZ7V2qW+Imdh+gccbYVQ9QDwFIcqE1nFxSsKxSOlC2usuep7QrVtolK3ztgRUNh+wycHQ3nAXCJmHYc4pVoRrI5/vShsIpJKh1PIF9BuY2Ee0yITwll2bPWffaRdprwWzjHRML0cRngEIpZfJqkVM5zECbsOhOOCXQm3BYmLKMnIkYtRDTARUl4JVOHuAsITFMc1DHuhhsKtczyJGoVVJwWUAobQwRyvKq7b0NKzTChWExckmLbRp2UYQPslC5ZgNRT1YzScDQCTzjikEWIa44y4spl/zuXxSFRITOaTnyZsxBfU/YgWhNGOQ0oKQLrjhVBBWRym6LfS5RybQ+ILc/rrOeZqoijhCaoEVrKBFV+CwTZiLUkvjkOw2yftMVqPbCPEKGO0+DQasmV3jEj1DQ1Vq9p8CCwBLcznERLAhnm2xwIDnEie45xMoUwjTOf8qFIZgbl/wA0jJox2UJDCXmVBmD0xgPYF10zxFYSdOMiLxb+szKoI22e9sQaDRtEFBVsNR6GLUTYTl88WBoPWaFwDui7AFw63fDvLx/pNXKhc3xx3RExADI4pq+aD3fYlY/+4qFTLf01zTJUztCrrqltp2NN7fQWIe6+k8InGGUVY8dc9XJQd06P+rAlDs5MymgKZGTXXXSVMpomajrqW8routTK9cmCbDVaz1edeHbb+dOmT0BfcQf0ekwjKBJILZqSQd5MIBe4J5kFGNIalCImH7UXNfWKE94VkxjPRdM0m+EChhqYx1YZnobma1B7j+ycR3PlkdEv9sNjGk/ciTWPX1ZZ+yz8N3UFoYLDOrXwA/qLECX8zpUS7/RqsEj2j6hBxU4uB/38cksZZ/CFafUDMGZapxeMoE0wnlmhbDVOfrcZIJ3f+ZsNR17D0Z5CWaALt5VhCzkgfcYX9mq/bopG7vGgVTjQhawpq/CZWBSvHIrNnxLsfJCoJPaP0bbc+RTxzS3jjPSM0Lb3+yvhden8ZfpCCZL96n8TwD4MYaYxtOuMgTbZZR+GMnG7+xNEHsm7/5JYN38B</diagram></mxfile>
\ No newline at end of file
+<mxfile modified="2022-11-15T11:35:34.226Z" host="app.diagrams.net" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" etag="v_puD-7TxF7KD0VfFUmk" version="20.4.0" type="device"><diagram id="g6TwU9qutF0pdJ5EpEK3" name="Page-1">5Vpbb5swFP41SNvDKjCXhMeGrtukXaJE26q9ueCCVwcz49z262eDSQCTlm2hWZeqEubY2Pj7zjk+XxLDDhabNwxmyQcaIWIAM9oY9pUBgOUAYMh/M9qWltHYKw0xw5EatDfM8U+kjKayLnGE8sZATinhOGsaQ5qmKOQNG2SMrpvD7ihprprBGGmGeQiJbv2KI54oq+c6+463CMdJtbTl+WXPAlaj1VbyBEZ0XTPZrw07YJTysrXYBIhI9CpgyueuD/Tu3oyhlPd5YPZlcuNnwdW3cP39G3qf//RmN6/UNlaQLNWOp1ue0FTYXqCL+EJcP1KObim9f6m2wbcVOIwu0wjJ6U3DnqwTzNE8g6HsXQt3ELaEL4i4s0RzhRjHAthLguNU2DiVA3LO6P0OWoHJRN+W2qmcAG1qJrXNN4guEGdbMUT1AltxoJzOqShY1ygcK1tSZ89X7gqV28S7uffAiobC9jdwdjWcDeARsewkwivRjGVz8uFdZRWL1DoeQb6GchcJd5iQgBLKimftu+JP2mnKr+ECEwnT+2WIIyhWCWiaU7nOUZhwmky4FtCZ8DqYsM2BiBh3ENECF6XRpUwd4i4kMM9x2MT4j3FDUSPZ6KjVUHE7QKlsDBHI8aqZorqQUitMKRZvsiPFccwmKaMW2DldshCpp+oZpTXRGDwyEYcsRlybqCBut+0/57I6JGpkxrNpIHMW4mvK7kVryiinISVVYN2yKqiATG4z9GOJcq75gPBv3mS9zFRVHKU0Ra3QUiao8lso2EesI/EtcBQVftIVq83AfoIIdd0Wh3ZHrvSfMkItS2P1iob3AktwfZllHOJUtiUMVfspzqUI5kkxp3wolnlBzX8EEsbtiOwgYQd4nQRgDsYCOBRbc8RWBQEvylLhL+uCJtLWac+r8ahFhNdxXo06iHDcoXiwNR6KMuEc0PcAuHD74T9cNtIr5ErlBOK6J8IAMjhm/zUf3qgvH8PFQ69K+nOujgjJ0H9dUTtuz4raHSxCvEMnRUAwKurFnrnq+aDunh71UUccnJmQ0fTH2GlO0VfIaIqoPdHQQkZXpXapTjKy1Wg9X23iO13nT5c6AUPFHdDrMY2gWCCVtSWDvJlCLnBPCwswpTXciZhyVH/Udp95wttqXfNBNC2rHS5gpIH51CrD19B8ZlrvYSd53NVPh33lDQ8pPHEn9jz5h4vav8V/09QPKjTsU8s+oH8IomTfuVLin14LVqn+AS2o2CnFYFBerinjDP7LSn0Yxiz79HIRdMnFMyuT7da577UDpPfn/VZrIr810YEyWaALt7VhmRyQ/8YL+41vNmVtUMx41Boc6DLWkjX4XGyK1w7F9tcITjlIVBKHx2gudz4lfNtl3LGeEbp8f7gCXhfOn2bPlCDZr34zAZzjEGaZI6fJGOgSXc5xKBO3+x9AlJG8/x2J/foX</diagram></mxfile>
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 40060e3..4d24923 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -30,7 +30,12 @@
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', 'sphinxarg.ext']
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode',
+              'sphinx.ext.napoleon', 'sphinx.ext.intersphinx',
+              'sphinx.ext.autosectionlabel',
+              'sphinxarg.ext', 'sphinxcontrib.apidoc',
+              'sphinx_copybutton',
+              ]
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -54,16 +59,16 @@
 # built documents.
 #
 # The short X.Y version.
-version = u'0.2'
+version = u'0.4.0'
 # The full version, including alpha/beta/rc tags.
-release = u'0.2'
+release = version
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 #
 # This is also used if you do content translation via gettext catalogs.
 # Usually you set "language" from the command line for these cases.
-language = None
+language = 'en'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -76,6 +81,11 @@
 # If true, `todo` and `todoList` produce output, else they produce nothing.
 todo_include_todos = False
 
+apidoc_module_dir = "../grpc4bmi"
+apidoc_separate_modules = True
+apidoc_toc_file = False
+# Ignore generated files
+apidoc_excluded_paths = ["bmi_pb2_grpc.py", "bmi_pb2.py"]
 
 # -- Options for HTML output ----------------------------------------------
 
@@ -167,4 +177,5 @@
 intersphinx_mapping = {
     'https://docs.python.org/': None,
     'numpy': ('http://docs.scipy.org/doc/numpy', None),
+    "bmipy": ("https://bmi.readthedocs.io/en/latest/", None),
 }
diff --git a/docs/container/usage.rst b/docs/container/usage.rst
index 3ec8fa0..1dbdf88 100644
--- a/docs/container/usage.rst
+++ b/docs/container/usage.rst
@@ -10,15 +10,14 @@ Grpc4bmi can run containers with `Docker engine`_.
 
 Use the :class:`grpc4bmi.bmi_client_docker.BmiClientDocker` class to start a Docker container and get a client to interact with the model running inside the container.
 
-
-
 For example the PCR-GLOBWB model can be started in a Docker container with
 
 .. code-block:: python
 
+    from grpc4bmi.bmi_client_docker import BmiClientDocker
+
     model = BmiClientDocker(image='ewatercycle/pcrg-grpc4bmi:latest', image_port=55555,
-                            input_dir="./input",
-                            output_dir="./output")
+                            work_dir="./input")
     # Interact with model
     model.initialize('config.cfg')
 
@@ -27,6 +26,8 @@ For example the PCR-GLOBWB model can be started in a Docker container with
 
 .. _Docker engine: https://docs.docker.com/
 
+.. _Singularity-section:
+
 Singularity
 -----------
 
@@ -48,15 +49,65 @@ Use the :class:`grpc4bmi.bmi_client_singularity.BmiClientSingularity` class to s
 .. code-block:: python
 
     from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+
     image = '<docker image name of grpc4bmi server of a bmi model>'
-    client = BmiClientSingularity(image, input_dir='<directory with models input data files>')
+    input_dir = '<directory with models input data files>'
+    work_dir = '<directory where model can write output files>'
+    client = BmiClientSingularity(image, work_dir, input_dirs=[input_dir])
+
+     # Interact with model
+    client.initialize('<absolute path to config file in the input directory>')
+
+    # Stop container
+    del client
 
 For example for the wflow Docker image the commands would be the following
 
 .. code-block:: python
 
     from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+
     image = 'docker://ewatercycle/wflow-grpc4bmi:latest'
-    client = BmiClientSingularity(image, input_dir='wflow_rhine_sbm', output_dir='wflow_output')
+    work_dir = '/tmp/run1'
+    client = BmiClientSingularity(image, work_dir, input_dirs=['/scratch/input/wflow_rhine_sbm'])
+
+     # Interact with model
+    client.initialize('/scratch/input/wflow_rhine_sbm/wflow_sbm_bmi.ini')
+
+    # Stop container
+    del client
+
 
 .. _Singularity: https://www.sylabs.io/guides/latest/user-guide/
+
+Apptainer
+---------
+
+Grpc4bmi can run containers on `Apptainer`_.
+
+Apptainer is an open source fork of singularity.
+
+Apptainer behaves very similar to singularity so everything described in :ref:`Singularity-section` chapter also goes for Apptainer.
+You will need to replace the `singularity` command with `apptainer` and :class:`grpc4bmi.bmi_client_singularity.BmiClientSingularity` class with
+:class:`grpc4bmi.bmi_client_apptainer.BmiClientApptainer` class.
+
+.. _Apptainer: https://apptainer.org/docs/user/main/
+
+Sharing files between host computer and container
+-------------------------------------------------
+
+Containers run in an isolated file system and by default can not read/write any files on host computer.
+To get a directory on your host computer inside a container you have mount them with `input_dirs` and
+`work_dir` arguments of :py:class:`grpc4bmi.bmi_client_docker.BmiClientDocker` and
+:py:class:`grpc4bmi.bmi_client_singularity.BmiClientSingularity`.
+
+See :py:class:`grpc4bmi.bmi_client_singularity.BmiClientSingularity` for examples using `input_dirs` and `work_dir`.
+
+Support for legacy container images
+-----------------------------------
+
+A legacy container image is an image with grpc4bmi version < 0.3 inside,
+which uses BMI v0.2.
+The grpc4bmi version >= 0.3 uses BMI v2.0.
+These legacy container images can be called with grpc4bmi>=0.3,
+but some methods will not work, see :ref:`Legacy version` for list.
diff --git a/docs/introduction.rst b/docs/introduction.rst
index 86ddfe5..ce70bf8 100644
--- a/docs/introduction.rst
+++ b/docs/introduction.rst
@@ -3,6 +3,6 @@ Introduction
 
 The Basic Modeling Interface (BMI, see https://github.com/csdms/bmi) is a multi-language library interface tailored to earth system models. This software allows you to wrap a BMI implementation in a server process and communicate with it via the included python client. The communication is serialized to protocol buffers by GRPC (https://grpc.io/) and occurs over network ports. On the server side, we support BMI implementations in :ref:`python <pythonservice>`, R or C/C++. Fortran models need to be linked against the C-version of the BMI. On the client side, we expose the standard python BMI (https://github.com/csdms/bmi-python/).
 
-This setup enables you to wrap your BMI-enabled model in a Docker (https://www.docker.com/) or Singularity (https://singularity.lbl.gov/) container  and communicate with it from a python process on the host machine.
+This setup enables you to wrap your BMI-enabled model in a Docker (https://www.docker.com/) or Singularity (https://singularity.lbl.gov/) or Apptainer (https://apptainer.org) container  and communicate with it from a python process on the host machine.
 
 .. image:: _static/design-overview.svg
diff --git a/docs/python_api.rst b/docs/python_api.rst
index c17beaa..339cc58 100644
--- a/docs/python_api.rst
+++ b/docs/python_api.rst
@@ -4,4 +4,4 @@ Python API
 .. toctree::
    :maxdepth: 4
 
-   grpc4bmi
+   api/grpc4bmi
diff --git a/docs/server/Cpp.rst b/docs/server/Cpp.rst
index fe8dd49..aace88c 100644
--- a/docs/server/Cpp.rst
+++ b/docs/server/Cpp.rst
@@ -20,6 +20,16 @@ For native programming languages it is necessary to install and compile the C++
     sudo make -j4 install
     sudo ldconfig
 
+You will also need the install the BMI C and C++ headers
+
+.. code-block:: sh
+
+    git clone -b v2.0 https://github.com/csdms/bmi-cxx.git bmi-cxx
+    cd bmi-cxx && mkdir build && cd build cmake .. && make install
+
+    git clone -b v2.1 https://github.com/csdms/bmi-c.git bmi-c
+    cd bmi-c && mkdir build && cd build cmake .. && make install
+
 You will also need to compile grpc4bmi
 
 .. code-block:: sh
@@ -33,29 +43,41 @@ You will also need to compile grpc4bmi
 Creating
 --------
 
-The grpc4bmi package comes with a C++ abstract base class that contains the BMI functions. The `header file <https://github.com/eWaterCycle/grpc4bmi/blob/master/cpp/bmi_class.h>`_ will
-be copied to your system include path upon the installation steps above. Write an implementation of the ``Bmi`` class using your model time step code and data structures. You don't have to worry about global variables in your model code: with grpc4bmi every model instance runs in its own memory space. For the same reason, the ``get_value_ptr`` and ``set_value_ptr`` methods can be safely ignored, they are never called through the grpc process bridge.
+The grpc4bmi package requires a model implementing the BMI interface in 
+`C with bmi.h <https://github.com/csdms/bmi-c/blob/v2.1/bmi.h>`_ 
+or `C++ with bmi.hxx <https://github.com/csdms/bmi-cxx/blob/v2.0/bmi.hxx>`_. 
+The header files will
+be copied to your system include path upon the installation steps above. 
+Write an implementation of the ``Bmi`` class using your model time step code and data structures. 
+You don't have to worry about global variables in your model code: 
+with grpc4bmi every model instance runs in its own memory space. 
+For the same reason, the ``get_value_ptr`` and ``set_value_ptr`` methods can be safely ignored, 
+they are never called through the grpc network protocol.
 
 Running
 -------
 
-Since native language lack reflection, it is necessary to make your own ``run_bmi_server`` program. We provide a function ``run_bmi_server(Bmi*, int*, char*)`` in the ``bmi_grpc_server.h`` header that can be called with your model instance (see the example below). To compile your server binary, it is necessary to link against grpc4bmi and protobuf libraries.
-The program will accept a single optional argument which is the port the server will run on. The port can also be specified using the BMI_PORT environment variable. The default port is 50051.
+Since native language lack reflection, it is necessary to make your own ``run_bmi_server`` program. 
+We provide a function ``run_bmi_server(Bmi*, int*, char*)`` in the ``bmi_grpc_server.h`` header 
+that can be called with your model instance (see the example below). To compile your server binary, 
+it is necessary to link against grpc4bmi and protobuf libraries.
+The program will accept a single optional argument which is the port the server will run on.
+The port can also be specified using the BMI_PORT environment variable. The default port is 50051.
 
 .. _example_cpp:
 
-Example
--------
+Example in C
+------------
 
-To create a BMI to your model, write a header file in which you declare the overridden functions of the base class ``Bmi`` in the included file ``bmi_class.h``.
+To create a BMI to your model, write a header file in which you declare the overridden functions of the base class ``Bmi`` in the included file ``bmi.h``.
 
 my_bmi_model.h:
 
 .. code-block:: cpp
 
-    #include <bmi_class.h>
+    #include <bmi.h>
 
-    class MyBmiModel: public Bmi
+    class MyBmiModel: public bmi::Bmi
     {
         public:
             MyBmiModel();
diff --git a/docs/server/python.rst b/docs/server/python.rst
index c2c19d4..3dfc3c3 100644
--- a/docs/server/python.rst
+++ b/docs/server/python.rst
@@ -49,7 +49,7 @@ and inside the ``mymodule.py`` the bmi implementation
 
 .. code-block:: python
 
-    from bmi import Bmi
+    from bmipy import Bmi
 
     class MyBmi(Bmi):
         def __init__(self):
@@ -63,4 +63,33 @@ Then we launch this toy model as a service by executing
 
     $ run-bmi-server --name mypackage.mymodule.MyBmi
 
-This will report the chosen port number in the standard output stream. It can be used to connect to the service via the BMI :ref:`grpc python client <python-grpc4bmi-client>`.
\ No newline at end of file
+This will report the chosen port number in the standard output stream. It can be used to connect to the service via the BMI :ref:`grpc python client <python-grpc4bmi-client>`.
+
+Legacy version
+--------------
+
+The grpc4bmi by default uses the `v2.0 version of the Basic Model Interface (BMI) <https://pypi.org/project/bmipy/2.0/>`_ on the client and server side.
+Grpc4bmi has support for `legacy version 0.2 of BMI <https://pypi.org/project/basic-modeling-interface/0.2/>`_ on the server side.
+
+A legacy model can be launched with
+
+.. code-block:: sh
+
+    $ run-bmi-server --bmi-version 0.2 --name mypackage.mymodule.MyLegacyBmi
+
+The functions only present in BMI v0.2 will not be callable:
+
+- update_frac
+- get_grid_connectivity
+- get_grid_offset
+
+The functions only present in BMI v2.0 will throw not implemented errors:
+
+- get_var_location
+- get_grid_node_count
+- get_grid_edge_count
+- get_grid_face_count
+- get_grid_edge_nodes
+- get_grid_face_nodes
+- get_grid_face_edges
+- get_grid_nodes_per_face
diff --git a/grpc4bmi/bmi_client_apptainer.py b/grpc4bmi/bmi_client_apptainer.py
new file mode 100644
index 0000000..9daccf4
--- /dev/null
+++ b/grpc4bmi/bmi_client_apptainer.py
@@ -0,0 +1,275 @@
+import logging
+import os
+import subprocess
+import time
+from os.path import abspath
+from tempfile import SpooledTemporaryFile
+from typing import Iterable
+
+from packaging.specifiers import SpecifierSet
+from packaging.version import Version
+from typeguard import typechecked
+
+from grpc4bmi.bmi_grpc_client import BmiClient
+from grpc4bmi.exceptions import ApptainerVersionException, DeadContainerException
+
+SUPPORTED_APPTAINER_VERSIONS = '>=1.0.0-rc.2'  # First apptainer release with binaries
+
+def check_apptainer_version_string(version_output: str) -> bool:
+    version = version_output.split(' ').pop()
+    local_version = Version(version)
+    if local_version not in SpecifierSet(SUPPORTED_APPTAINER_VERSIONS):
+        raise ApptainerVersionException(f'Unsupported version ({version_output}) of apptainer found, '
+                                        f'supported versions {SUPPORTED_APPTAINER_VERSIONS}')
+    return True
+
+def check_apptainer_version():
+    p = subprocess.Popen(['apptainer', '--version'], stdout=subprocess.PIPE)
+    (stdout, _) = p.communicate()
+    if p.returncode != 0:
+        raise ApptainerVersionException('Unable to determine apptainer version')
+    check_apptainer_version_string(stdout.decode('utf-8'))
+
+
+class BmiClientApptainer(BmiClient):
+    """BMI GRPC client for model running inside a `Apptainer <https://apptainer.org/>`_ container.
+
+    On instantization launches an Apptainer container.
+    The Apptainer container image is expected to run a BMI GRPC server as its default command.
+    The client picks a random port and expects the container to run the BMI GRPC server on that port.
+    The port is passed to the container using the BMI_PORT environment variable.
+
+    Args:
+        image: Apptainer image.
+
+            For Docker Hub image use `docker://*` or convert it to a Apptainer image file.
+
+            To convert Docker image
+            `ewatercycle/walrus-grpc4bmi <https://hub.docker.com/layers/ewatercycle/walrus-grpc4bmi>`_
+            with `v0.2.0` tag to `./ewatercycle-walrus-grpc4bmi_v0.2.0.sif` Apptainer image file use:
+
+            .. code-block:: console
+
+              apptainer pull ewatercycle-walrus-grpc4bmi_v0.2.0.sif \\
+                docker://ewatercycle/walrus-grpc4bmi:v0.2.0
+
+        input_dirs (Iterable[str]): Directories for input files of model.
+
+            All of directories will be mounted read-only inside Apptainer container on same path as outside container.
+
+        work_dir (str): Working directory for model.
+
+            Directory is mounted inside container and changed into.
+
+            To create a random work directory you could use
+
+            .. code-block:: python
+
+              from tempfile import TemporaryDirectory
+              from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+
+              work_dir = TemporaryDirectory()
+
+              image = 'ewatercycle-walrus-grpc4bmi_v0.2.0.sif'
+              client =  BmiClientApptainer(image, work_dir.name)
+
+              # Write config to work_dir and interact with client
+
+              # After checking output in work_dir, clean up
+              work_dir.cleanup()
+
+        delay (int): Seconds to wait for Apptainer container to startup, before connecting to it
+
+            Increase when container takes a long time to startup.
+
+        timeout (int): Seconds to wait for gRPC client to connect to server.
+
+            By default will try forever to connect to gRPC server inside container.
+            Set to low number to escape endless wait.
+
+        capture_logs (bool): Whether to capture stdout and stderr of container .
+
+            If false then redirects output to null device never to be seen again.
+            If true then redirects output to temporary file which can be read with :py:func:`BmiClientApptainer.logs()`.
+            The temporary file gets removed when this object is deleted.
+
+    **Example 1: Config file already inside image**
+
+    MARRMoT has an `example config file <https://github.com/wknoben/MARRMoT/blob/master/BMI/Config/BMI_testcase_m01_BuffaloRiver_TN_USA.mat>`_ inside its Docker image.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+        client = BmiClientApptainer(image='docker://ewatercycle/marrmot-grpc4bmi:latest', 
+                                    work_dir='/opt/MARRMoT/BMI/Config')
+        client.initialize('/opt/MARRMoT/BMI/Config/BMI_testcase_m01_BuffaloRiver_TN_USA.mat')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 2: Config file in input directory**
+
+    The config file and all other files the model needs are in a directory (`/tmp/input`).
+    Use `/tmp/work` to capture any output files like logs generated by model.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+        # Generate config file called 'config.mat' in `/tmp/input` directory
+        client = BmiClientApptainer(image='docker://ewatercycle/marrmot-grpc4bmi:latest',
+                                    input_dirs=['/tmp/input'],
+                                    work_dir='/tmp/work')
+        client.initialize('/tmp/input/config.mat')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 3: Read only input directory with config file in work directory**
+
+    The forcing data is in a shared read-only location like `/shared/forcings/walrus`.
+    In the config file (`/tmp/work/walrus.yml`) point to a forcing data file (`/shared/forcings/walrus/PEQ_Hupsel.dat`).
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+        client = BmiClientApptainer(image='ewatercycle-walrus-grpc4bmi_v0.2.0.sif',
+                                    input_dirs=['/shared/forcings/walrus'],
+                                    work_dir='/tmp/work')
+        client.initialize('walrus.yml')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 4: Model writes in sub directory of input directory**
+
+    Some models, for example wflow, write output in a sub-directory of the input directory.
+    If the input directory is set with the `input_dirs` argument then the model will be unable to write its output as
+    input directories are mounted read-only. That will most likely cause the model to die.
+    A workaround is to use the `work_dir` argument with input directory as value instead.
+    This will make the whole input directory writable so the model can do its thing.
+
+    When input directory is on a shared disk where you do not have write permission then
+    the input dir should be copied to a work directory (`/scratch/wflow`) so model can write.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+        client = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                    work_dir='/scratch/wflow')
+        client.initialize('wflow_sbm.ini')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 5: Inputs are in multiple directories**
+
+    A model has its forcings (`/shared/forcings/muese`), parameters (`/shared/model/wflow/staticmaps`)
+    and config file (`/tmp/work/wflow_sbm.ini`) in different locations.
+    The config file should be set to point to the forcing and parameters files.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+        client = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                    input_dirs=['/shared/forcings/muese',
+                                                '/shared/model/wflow/staticmaps'],
+                                    work_dir='/tmp/work')
+        client.initialize('wflow_sbm.ini')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 6: Run model twice with their own work directory**
+
+    While running 2 or models at the same time you do not want the any config or output to be mixed.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
+        client_muese = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                          work_dir='/scratch/wflow-meuse')
+        client_muese.initialize('wflow_sbm.meuse.ini')
+        client_rhine = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                          work_dir='/scratch/wflow-rhine')
+        client_rhine.initialize('wflow_sbm.rhine.ini')
+        ...
+        # Run models and set/get values
+        ...
+        del client_muese
+        del client_rhine
+
+    """
+
+    @typechecked
+    def __init__(self, image: str, work_dir: str, input_dirs: Iterable[str] = tuple(), delay=0, timeout=None,
+                 capture_logs=True,
+                 ):
+        if type(input_dirs) == str:
+            msg = f'type of argument "input_dirs" must be collections.abc.Iterable; ' \
+                  f'got {type(input_dirs)} instead'
+            raise TypeError(msg)
+        check_apptainer_version()
+        host = 'localhost'
+        port = BmiClient.get_unique_port(host)
+        args = [
+            "apptainer",
+            "run",
+            "--contain",
+            "--env", f"BMI_PORT={port}"
+        ]
+
+        for raw_input_dir in input_dirs:
+            input_dir = abspath(raw_input_dir)
+            if not os.path.isdir(input_dir):
+                raise NotADirectoryError(input_dir)
+            args += ["--bind", f'{input_dir}:{input_dir}:ro']
+        self.work_dir = abspath(work_dir)
+        if self.work_dir in {abspath(d) for d in input_dirs}:
+            raise ValueError('Found work_dir equal to one of the input directories. Please drop that input dir.')
+        if not os.path.isdir(self.work_dir):
+            raise NotADirectoryError(self.work_dir)
+        args += ["--bind", f'{self.work_dir}:{self.work_dir}:rw']
+        # Change into working directory
+        args += ["--pwd", self.work_dir]
+        args.append(image)
+        logging.info(f'Running {image} apptainer container on port {port}')
+        if capture_logs:
+            self.logfile = SpooledTemporaryFile(max_size=2 ** 16,  # keep until 65Kb in memory if bigger write to disk
+                                                prefix='grpc4bmi-apptainer-log',
+                                                mode='w+t',
+                                                encoding='utf8')
+            stdout = self.logfile
+        else:
+            stdout = subprocess.DEVNULL
+        self.container = subprocess.Popen(args, preexec_fn=os.setsid, stderr=subprocess.STDOUT, stdout=stdout)
+        time.sleep(delay)
+        returncode = self.container.poll()
+        if returncode is not None:
+            raise DeadContainerException(
+                f'apptainer container {image} prematurely exited with code {returncode}',
+                returncode,
+                self.logs()
+            )
+        super(BmiClientApptainer, self).__init__(BmiClient.create_grpc_channel(port=port, host=host), timeout=timeout)
+
+    def __del__(self):
+        if hasattr(self, "container"):
+            self.container.terminate()
+            self.container.wait()
+        if hasattr(self, "logfile"):
+            # Force deletion of log file
+            self.logfile.close()
+
+    def get_value_ref(self, var_name):
+        raise NotImplementedError("Cannot exchange memory references across process boundary")
+
+    def logs(self) -> str:
+        """Returns complete combined stdout and stderr written by the Apptainer container.
+
+        When object was created with `log_enable=False` argument then always returns empty string.
+        """
+        if not hasattr(self, "logfile"):
+            return ''
+
+        current_position = self.logfile.tell()
+        # Read from start
+        self.logfile.seek(0)
+        content = self.logfile.read()
+        # Write from last position
+        self.logfile.seek(current_position)
+        return content
diff --git a/grpc4bmi/bmi_client_docker.py b/grpc4bmi/bmi_client_docker.py
index 6cbc7e8..328400a 100644
--- a/grpc4bmi/bmi_client_docker.py
+++ b/grpc4bmi/bmi_client_docker.py
@@ -1,49 +1,39 @@
 import os
-import errno
 import time
+from os.path import abspath
+from typing import Iterable
 
 import docker
+from docker.models.containers import Container
+from typeguard import typechecked
 
 from grpc4bmi.bmi_grpc_client import BmiClient
-from grpc4bmi.utils import stage_config_file
-
-
-class LogsException(Exception):
-    pass
-
-
-class DeadDockerContainerException(ChildProcessError):
-    """
-    Exception for when a Docker container has died.
-
-    Args:
-        message (str): Human readable error message
-        exitcode (int): The non-zero exit code of the container
-        logs (str): Logs the container produced
-
-    """
-    def __init__(self, message, exitcode, logs, *args):
-        super().__init__(message, *args)
-        #: Exit code of container
-        self.exitcode = exitcode
-        #: Stdout and stderr of container
-        self.logs = logs
+from grpc4bmi.exceptions import DeadContainerException
 
 
 class BmiClientDocker(BmiClient):
     """
     BMI gRPC client for dockerized server processes: the initialization launches the docker container which should have the
-    run-bmi-server as its command. Also, it should expose the tcp port 55555 for communication with this client. Upon
+    run-bmi-server as its command. Also, it should expose the tcp port 50001 for communication with this client. Upon
     destruction, this class terminates the corresponding docker server.
 
     Args:
         image (str): Docker image name of grpc4bmi wrapped model
         image_port (int): Port of server inside the image
         host (str): Host on which the image port is published on a random port
-        input_dir (str): Directory for input files of model
-        output_dir (str): Directory for input files of model
-        user (str): Username or UID of Docker container
+        input_dirs (Iterable[str]): Directories for input files of model.
+
+            All of directories will be mounted read-only inside Docker container on same path as outside container.
+
+        work_dir (str): Working directory for model.
+
+            Directory is mounted inside container and changed into.
+
+        user (str): Username or UID of Docker container. Defaults to own UID.
         remove (bool): Automatically remove the container and logs when it exits.
+
+            Enable to get logs when container dies prematurely.
+
         delay (int): Seconds to wait for Docker container to startup, before connecting to it
         timeout (int): Seconds to wait for gRPC client to connect to server
         extra_volumes (Dict[str,Dict]): Extra volumes to attach to Docker container.
@@ -58,69 +48,63 @@ class BmiClientDocker(BmiClient):
 
                 {'/data/shared/forcings/': {'bind': '/forcings', 'mode': 'ro'}}
 
-    """
+            Increase when container takes a long time to startup.
+
+        timeout (int): Seconds to wait for gRPC client to connect to server.
 
-    input_mount_point = "/data/input"
-    output_mount_point = "/data/output"
+            By default will try forever to connect to gRPC server inside container.
+            Set to low number to escape endless wait.
 
-    def __init__(self, image, image_port=55555, host=None,
-                 input_dir=None, output_dir=None,
-                 user=os.getuid(), remove=False,
-                 delay=5, timeout=None, extra_volumes=None):
+    See :py:class:`grpc4bmi.bmi_client_apptainer.BmiClientApptainer` for examples using `input_dirs` and `work_dir`.
+    """
+
+    @typechecked
+    def __init__(self, image: str, work_dir: str, image_port=50051, host=None,
+                 input_dirs: Iterable[str] = tuple(),
+                 user=os.getuid(), remove=False, delay=5,
+                 timeout=None):
+        if type(input_dirs) == str:
+            msg = f'type of argument "input_dirs" must be collections.abc.Iterable; ' \
+                  f'got {type(input_dirs)} instead'
+            raise TypeError(msg)
         port = BmiClient.get_unique_port()
         client = docker.from_env()
         volumes = {}
-        if extra_volumes is not None:
-            volumes.update(extra_volumes)
-        self.input_dir = None
-        if input_dir is not None:
-            self.input_dir = os.path.abspath(input_dir)
-            if not os.path.isdir(self.input_dir):
+        for raw_input_dir in input_dirs:
+            input_dir = abspath(raw_input_dir)
+            if not os.path.isdir(input_dir):
                 raise NotADirectoryError(input_dir)
-            volumes[self.input_dir] = {"bind": BmiClientDocker.input_mount_point, "mode": "rw"}
-        self.output_dir = None
-        if output_dir is not None:
-            self.output_dir = os.path.abspath(output_dir)
-            try:
-                # Create output dir ourselves, otherwise Docker will create it as root user, resulting in permission
-                # errors
-                os.mkdir(self.output_dir)
-            except OSError as e:
-                if e.errno != errno.EEXIST:
-                    raise
-            volumes[self.output_dir] = {"bind": BmiClientDocker.output_mount_point, "mode": "rw"}
-        self.container = client.containers.run(image,
+            volumes[input_dir] = {"bind": input_dir, "mode": "ro"}
+
+        self.work_dir = abspath(work_dir)
+        if self.work_dir in volumes:
+            raise ValueError('Found work_dir equal to one of the input directories. Please drop that input dir.')
+        if not os.path.isdir(self.work_dir):
+            raise NotADirectoryError(self.work_dir)
+        volumes[self.work_dir] = {"bind": self.work_dir, "mode": "rw"}
+        self.container: Container = client.containers.run(image,
                                                ports={str(image_port) + "/tcp": port},
                                                volumes=volumes,
+                                               working_dir=self.work_dir,
                                                user=user,
                                                remove=remove,
                                                detach=True)
         time.sleep(delay)
         if not remove:
-            # Only able to reload, read logs when container is not in auto remove mode
+            # Only able to reload, read logs on exited container when remove=False
             self.container.reload()
             if self.container.status == 'exited':
                 exitcode = self.container.attrs["State"]["ExitCode"]
-                logs = self.container.logs()
+                logs = self.logs()
                 msg = f'Failed to start Docker container with image {image}, Container log: {logs}'
-                raise DeadDockerContainerException(msg, exitcode, logs)
+                raise DeadContainerException(msg, exitcode, logs)
 
         super(BmiClientDocker, self).__init__(BmiClient.create_grpc_channel(port=port, host=host), timeout=timeout)
 
     def __del__(self):
-        if hasattr(self, "container"):
-            self.container.stop()
-
-    def initialize(self, filename):
-        fn = stage_config_file(filename, self.input_dir, self.input_mount_point)
-        super(BmiClientDocker, self).initialize(fn)
-
-    def get_value_ptr(self, var_name):
-        raise NotImplementedError("Cannot exchange memory references across process boundary")
-
-    def logs(self):
-        """Logs of the Docker container"""
-        try:
-            return self.container.logs()
-        except docker.errors.APIError as e:
-            raise LogsException("Unable to fetch logs, try pass remove=False to BmiClientDocker constructor, so logs are retained after container dies") from e
+        self.container.stop()
+
+    def logs(self) -> str:
+        """Returns complete combined stdout and stderr written by the Docker container.
+        """
+        return self.container.logs().decode('utf8')
diff --git a/grpc4bmi/bmi_client_singularity.py b/grpc4bmi/bmi_client_singularity.py
index f5b7cda..c3d3a61 100644
--- a/grpc4bmi/bmi_client_singularity.py
+++ b/grpc4bmi/bmi_client_singularity.py
@@ -1,26 +1,39 @@
-import errno
+import logging
 import os
+import subprocess
 import time
 from os.path import abspath
-import subprocess
-import logging
+from tempfile import SpooledTemporaryFile
+from typing import Iterable
 
-from semver import VersionInfo
+from packaging.specifiers import SpecifierSet
+from packaging.version import Version
+from typeguard import typechecked
 
 from grpc4bmi.bmi_grpc_client import BmiClient
-from grpc4bmi.utils import stage_config_file
+from grpc4bmi.exceptions import ApptainerVersionException, DeadContainerException, SingularityVersionException
 
-REQUIRED_SINGULARITY_VERSION = '>=3.6.0'
+SUPPORTED_SINGULARITY_VERSIONS = '>=3.6.0'
+SUPPORTED_APPTAINER_VERSIONS = '>=1.0.0-rc.2'  # First apptainer release with binaries
 
+def check_singularity_version_string(version_output: str) -> bool:
+    (app, _, version) = version_output.split(' ')
+    local_version = Version(version)
+    if app == 'singularity' and local_version not in SpecifierSet(SUPPORTED_SINGULARITY_VERSIONS):
+        raise SingularityVersionException(f'Unsupported version ({version_output}) of singularity found, '
+                                          f'supported versions {SUPPORTED_SINGULARITY_VERSIONS}')
+    # Apptainer creates /usr/bin/singularity symlink, so if installed will report the apptainer version.
+    if app == 'apptainer' and local_version not in SpecifierSet(SUPPORTED_APPTAINER_VERSIONS):
+        raise ApptainerVersionException(f'Unsupported version ({version_output}) of apptainer found, '
+                                          f'supported versions {SUPPORTED_APPTAINER_VERSIONS}')
+    return True
 
 def check_singularity_version():
-    p = subprocess.Popen(['singularity', 'version'], stdout=subprocess.PIPE)
-    (stdout, _stderr) = p.communicate()
+    p = subprocess.Popen(['singularity', '--version'], stdout=subprocess.PIPE)
+    (stdout, _) = p.communicate()
     if p.returncode != 0:
-        raise Exception('Unable to determine singularity version')
-    if not VersionInfo.parse(stdout.decode('utf-8')).match(REQUIRED_SINGULARITY_VERSION):
-        raise Exception(f'Wrong version of singularity found, require version {REQUIRED_SINGULARITY_VERSION}')
-    return True
+        raise SingularityVersionException('Unable to determine singularity version')
+    check_singularity_version_string(stdout.decode('utf-8'))
 
 
 class BmiClientSingularity(BmiClient):
@@ -29,73 +42,237 @@ class BmiClientSingularity(BmiClient):
     The client picks a random port and expects the container to run the server on that port.
     The port is passed to the container using the BMI_PORT environment variable.
 
-    >>> from grpc4bmi.bmi_client_singularity import BmiClientSingularity
-    >>> image = 'docker://ewatercycle/wflow-grpc4bmi:latest'
-    >>> client = BmiClientSingularity(image, input_dir='wflow_rhine_sbm', output_dir='wflow_output')
-    >>> client.initialize('wflow_rhine_sbm/wflow_sbm.ini')
-    >>> client.update_until(client.get_end_time())
-    >>> del client
-
     Args:
-        image: Singularity image. For Docker Hub image use `docker://*`.
-        input_dir (str): Directory for input files of model
-        output_dir (str): Directory for input files of model
-        timeout (int): Seconds to wait for gRPC client to connect to server
-        delay (int): Seconds to wait for Singularity container to startup, before connecting to it
-        extra_volumes (Dict[str,str]): Extra volumes to attach to Singularity container.
+        image: Singularity image.
+
+            For Docker Hub image use `docker://*` or convert it to a Singularity image file.
+
+            To convert Docker image
+            `ewatercycle/walrus-grpc4bmi <https://hub.docker.com/layers/ewatercycle/walrus-grpc4bmi>`_
+            with `v0.3.1` tag to `./ewatercycle-walrus-grpc4bmi_v0.3.1.sif` Singularity image file use:
+
+            .. code-block:: console
+
+              singularity pull ewatercycle-walrus-grpc4bmi_v0.3.1.sif \\
+                docker://ewatercycle/walrus-grpc4bmi:v0.3.1
+
+        input_dirs (Iterable[str]): Directories for input files of model.
+
+            All of directories will be mounted read-only inside Singularity container on same path as outside container.
 
-            The key is the hosts path and the value the mounted volume inside the container.
-            Contrary to Docker client, extra volumes are always read/write
+        work_dir (str): Working directory for model.
 
-            For example:
+            Directory is mounted inside container and changed into.
+
+            To create a random work directory you could use
 
             .. code-block:: python
 
-                    {'/data/shared/forcings/': '/data/forcings'}
+              from tempfile import TemporaryDirectory
+              from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+
+              work_dir = TemporaryDirectory()
+
+              image = 'ewatercycle-walrus-grpc4bmi_v0.2.0.sif'
+              client =  BmiClientSingularity(image, work_dir.name)
+
+              # Write config to work_dir and interact with client
+
+              # After checking output in work_dir, clean up
+              work_dir.cleanup()
+
+        delay (int): Seconds to wait for Singularity container to startup, before connecting to it
+
+            Increase when container takes a long time to startup.
+
+        timeout (int): Seconds to wait for gRPC client to connect to server.
+
+            By default will try forever to connect to gRPC server inside container.
+            Set to low number to escape endless wait.
+
+        capture_logs (bool): Whether to capture stdout and stderr of container .
+
+            If false then redirects output to null device never to be seen again.
+            If true then redirects output to temporary file which can be read with :py:func:`BmiClientSingularity.logs()`.
+            The temporary file gets removed when this object is deleted.
+
+    **Example 1: Config file already inside image**
+
+    MARRMoT has an `example config file <https://github.com/wknoben/MARRMoT/blob/master/BMI/Config/BMI_testcase_m01_BuffaloRiver_TN_USA.mat>`_ inside its Docker image.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+        client = BmiClientSingularity(image='docker://ewatercycle/marrmot-grpc4bmi:latest', 
+                                      work_dir='/opt/MARRMoT/BMI/Config'))
+        client.initialize('/opt/MARRMoT/BMI/Config/BMI_testcase_m01_BuffaloRiver_TN_USA.mat')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 2: Config file in input directory**
+
+    The config file and all other files the model needs are in a directory (`/tmp/input`).
+    Use `/tmp/work` to capture any output files like logs generated by model.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+        # Generate config file called 'config.mat' in `/tmp/input` directory
+        client = BmiClientSingularity(image='docker://ewatercycle/marrmot-grpc4bmi:latest',
+                                      input_dirs=['/tmp/input'],
+                                      work_dir='/tmp/work')
+        client.initialize('/tmp/input/config.mat')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 3: Read only input directory with config file in work directory**
+
+    The forcing data is in a shared read-only location like `/shared/forcings/walrus`.
+    In the config file (`/tmp/work/walrus.yml`) point to a forcing data file (`/shared/forcings/walrus/PEQ_Hupsel.dat`).
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+        client = BmiClientSingularity(image='ewatercycle-walrus-grpc4bmi_v0.2.0.sif',
+                                      input_dirs=['/shared/forcings/walrus'],
+                                      work_dir='/tmp/work')
+        client.initialize('walrus.yml')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 4: Model writes in sub directory of input directory**
+
+    Some models, for example wflow, write output in a sub-directory of the input directory.
+    If the input directory is set with the `input_dirs` argument then the model will be unable to write its output as
+    input directories are mounted read-only. That will most likely cause the model to die.
+    A workaround is to use the `work_dir` argument with input directory as value instead.
+    This will make the whole input directory writable so the model can do its thing.
+
+    When input directory is on a shared disk where you do not have write permission then
+    the input dir should be copied to a work directory (`/scratch/wflow`) so model can write.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+        client = BmiClientSingularity(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                      work_dir='/scratch/wflow')
+        client.initialize('wflow_sbm.ini')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 5: Inputs are in multiple directories**
+
+    A model has its forcings (`/shared/forcings/muese`), parameters (`/shared/model/wflow/staticmaps`)
+    and config file (`/tmp/work/wflow_sbm.ini`) in different locations.
+    The config file should be set to point to the forcing and parameters files.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+        client = BmiClientSingularity(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                      input_dirs=['/shared/forcings/muese',
+                                                  '/shared/model/wflow/staticmaps'],
+                                      work_dir='/tmp/work')
+        client.initialize('wflow_sbm.ini')
+        client.update_until(client.get_end_time())
+        del client
+
+    **Example 6: Run model twice with their own work directory**
+
+    While running 2 or models at the same time you do not want the any config or output to be mixed.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_client_singularity import BmiClientSingularity
+        client_muese = BmiClientSingularity(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                            work_dir='/scratch/wflow-meuse')
+        client_muese.initialize('wflow_sbm.meuse.ini')
+        client_rhine = BmiClientSingularity(image='docker://ewatercycle/wflow-grpc4bmi:latest',
+                                            work_dir='/scratch/wflow-rhine')
+        client_rhine.initialize('wflow_sbm.rhine.ini')
+        ...
+        # Run models and set/get values
+        ...
+        del client_muese
+        del client_rhine
 
     """
-    INPUT_MOUNT_POINT = "/data/input"
-    OUTPUT_MOUNT_POINT = "/data/output"
 
-    def __init__(self, image, input_dir=None, output_dir=None, timeout=None,
-                 delay=0, extra_volumes=None):
+    @typechecked
+    def __init__(self, image: str, work_dir: str, input_dirs: Iterable[str] = tuple(), delay=0, timeout=None,
+                 capture_logs=True,
+                 ):
+        if type(input_dirs) == str:
+            msg = f'type of argument "input_dirs" must be collections.abc.Iterable; ' \
+                  f'got {type(input_dirs)} instead'
+            raise TypeError(msg)
         check_singularity_version()
         host = 'localhost'
         port = BmiClient.get_unique_port(host)
         args = [
             "singularity",
             "run",
+            "--contain",
             "--env", f"BMI_PORT={port}"
         ]
-        mount_points = {} if extra_volumes is None else extra_volumes
-        if input_dir is not None:
-            mount_points[input_dir] = BmiClientSingularity.INPUT_MOUNT_POINT
-            self.input_dir = abspath(input_dir)
-        if any(mount_points):
-            args += ["--bind", ','.join([hp + ':' + ip for hp, ip in mount_points.items()])]
-        if output_dir is not None:
-            self.output_dir = abspath(output_dir)
-            try:
-                # Create output dir ourselves or singularity will complain
-                os.mkdir(self.output_dir)
-            except OSError as e:
-                if e.errno != errno.EEXIST:
-                    raise e
-            args += ["--bind", output_dir + ':' + BmiClientSingularity.OUTPUT_MOUNT_POINT]
+
+        for raw_input_dir in input_dirs:
+            input_dir = abspath(raw_input_dir)
+            if not os.path.isdir(input_dir):
+                raise NotADirectoryError(input_dir)
+            args += ["--bind", f'{input_dir}:{input_dir}:ro']
+        self.work_dir = abspath(work_dir)
+        if self.work_dir in {abspath(d) for d in input_dirs}:
+            raise ValueError('Found work_dir equal to one of the input directories. Please drop that input dir.')
+        if not os.path.isdir(self.work_dir):
+            raise NotADirectoryError(self.work_dir)
+        args += ["--bind", f'{self.work_dir}:{self.work_dir}:rw']
+        # Change into working directory
+        args += ["--pwd", self.work_dir]
         args.append(image)
         logging.info(f'Running {image} singularity container on port {port}')
-        self.container = subprocess.Popen(args, preexec_fn=os.setsid)
+        if capture_logs:
+            self.logfile = SpooledTemporaryFile(max_size=2 ** 16,  # keep until 65Kb in memory if bigger write to disk
+                                                prefix='grpc4bmi-singularity-log',
+                                                mode='w+t',
+                                                encoding='utf8')
+            stdout = self.logfile
+        else:
+            stdout = subprocess.DEVNULL
+        self.container = subprocess.Popen(args, preexec_fn=os.setsid, stderr=subprocess.STDOUT, stdout=stdout)
         time.sleep(delay)
+        returncode = self.container.poll()
+        if returncode is not None:
+            raise DeadContainerException(
+                f'singularity container {image} prematurely exited with code {returncode}',
+                returncode,
+                self.logs()
+            )
         super(BmiClientSingularity, self).__init__(BmiClient.create_grpc_channel(port=port, host=host), timeout=timeout)
 
     def __del__(self):
         if hasattr(self, "container"):
             self.container.terminate()
             self.container.wait()
-
-    def initialize(self, filename):
-        fn = stage_config_file(filename, self.input_dir, self.INPUT_MOUNT_POINT, home_mounted=True)
-        super(BmiClientSingularity, self).initialize(fn)
+        if hasattr(self, "logfile"):
+            # Force deletion of log file
+            self.logfile.close()
 
     def get_value_ptr(self, var_name):
         raise NotImplementedError("Cannot exchange memory references across process boundary")
+
+    def logs(self) -> str:
+        """Returns complete combined stdout and stderr written by the Singularity container.
+
+        When object was created with `log_enable=False` argument then always returns empty string.
+        """
+        if not hasattr(self, "logfile"):
+            return ''
+
+        current_position = self.logfile.tell()
+        # Read from start
+        self.logfile.seek(0)
+        content = self.logfile.read()
+        # Write from last position
+        self.logfile.seek(current_position)
+        return content
diff --git a/grpc4bmi/bmi_grpc_client.py b/grpc4bmi/bmi_grpc_client.py
index 9cd22e8..2db4cfe 100644
--- a/grpc4bmi/bmi_grpc_client.py
+++ b/grpc4bmi/bmi_grpc_client.py
@@ -3,17 +3,19 @@
 import os
 import socket
 from contextlib import closing
+from typing import Optional
 
 import numpy as np
 from bmipy import Bmi
 import grpc
 import numpy
+from typeguard import typechecked
 
 from grpc_status import rpc_status
 from google.rpc import error_details_pb2
 
 from . import bmi_pb2, bmi_pb2_grpc
-from .utils import GRPC_MAX_MESSAGE_LENGTH
+from .constants import GRPC_MAX_MESSAGE_LENGTH
 
 log = logging.getLogger(__name__)
 
@@ -92,7 +94,8 @@ def get_unique_port(host=None):
             s.bind(("" if host is None else host, 0))
             return int(s.getsockname()[1])
 
-    def initialize(self, filename):
+    @typechecked
+    def initialize(self, filename: Optional[str]):
         fname = "" if filename is None else filename
         try:
             return self.stub.initialize(bmi_pb2.InitializeRequest(config_file=fname))
@@ -192,7 +195,17 @@ def get_var_type(self, name):
 
     def get_var_itemsize(self, name):
         try:
-            return self.stub.getVarItemSize(bmi_pb2.GetVarRequest(name=name)).size
+            item_size = self.stub.getVarItemSize(bmi_pb2.GetVarRequest(name=name)).size
+            if item_size == 0:
+                # BMI < v2.0 did not have get_var_itemsize, so old server will return 0
+                # fallback to getting item size from var type
+                var_type = self.get_var_type(name)
+                try:
+                    item_size = numpy.dtype(var_type).itemsize
+                    log.info(f'get_var_itemsize returned 0, corrected to {item_size} using get_var_type.')
+                except TypeError:
+                    raise ValueError('get_var_itemsize returned 0, which is impossible')
+            return item_size
         except grpc.RpcError as e:
             handle_error(e)
 
diff --git a/grpc4bmi/bmi_grpc_legacy_server.py b/grpc4bmi/bmi_grpc_legacy_server.py
index 14b79f0..eb74c2b 100644
--- a/grpc4bmi/bmi_grpc_legacy_server.py
+++ b/grpc4bmi/bmi_grpc_legacy_server.py
@@ -191,9 +191,6 @@ def setValue(self, request, context):
         except Exception as e:
             self.exception_handler(e, context)
 
-    def setValuePtr(self, request, context):
-        raise NotImplementedError("Array references cannot be transmitted through this GRPC channel")
-
     def setValueAtIndices(self, request, context):
         try:
             index_array = numpy.array(request.indices)
diff --git a/grpc4bmi/bmi_memoized.py b/grpc4bmi/bmi_memoized.py
new file mode 100644
index 0000000..7053e97
--- /dev/null
+++ b/grpc4bmi/bmi_memoized.py
@@ -0,0 +1,187 @@
+from bmipy import Bmi
+
+
+class MemoizedBmi(Bmi):
+    """Wrapper around Bmi object that caches the return values of almost all methods.
+
+    Most BMI methods return same value each time it is called, so the results can be cached.
+    gRPC communication is expensive and can be sped up by caching.
+
+    The following methods are not cached:
+
+    * initialize
+    * update_*
+    * finalize
+    * get_current_time
+    * get_value_*
+    * set_value_*
+
+    The cache is cleared when initialize() is called.
+
+    Example:
+
+        A gRPC BMI server is running on localhost:1234, to cache it use the following.
+
+        >>> import grpc
+        >>> from grpc4bmi.bmi_grpc_client import BmiClient
+        >>> from grpc4bmi.bmi_memoized import MemoizedBmi
+        >>> slow_model = BmiClient(grpc.insecure_channel("localhost:1234"))
+        >>> model = MemoizedBmi(slow_model)
+        >>> print(model.get_component_name())
+        Hello world
+        >>> # Calling second time will return cached value
+        >>> # and not talk to server on "localhost:1234"
+        >>> print(model.get_component_name())
+        Hello world
+
+    """
+    def __init__(self, origin: Bmi):
+        self.origin = origin
+        self.cache = dict()
+
+    def _cache(self, fn, arg=None, output=None):
+        if fn not in self.cache:
+            self.cache[fn] = dict()
+        if arg not in self.cache[fn]:
+            if arg is None:
+                self.cache[fn][arg] = getattr(self.origin, fn)()
+            else:
+                if output is None:
+                    self.cache[fn][arg] = getattr(self.origin, fn)(arg)
+                else:
+                    self.cache[fn][arg] = getattr(self.origin, fn)(arg, output)
+        return self.cache[fn][arg]
+
+    def initialize(self, filename):
+        self.cache = dict()
+        return self.origin.initialize(filename)
+
+    def update(self):
+        self.origin.update()
+
+    def update_until(self, time):
+        self.origin.update_until(time)
+
+    def finalize(self):
+        self.origin.finalize()
+
+    def get_component_name(self):
+        return self._cache('get_component_name')
+
+    def get_input_item_count(self):
+        return self._cache('get_input_item_count')
+
+    def get_output_item_count(self):
+        return self._cache('get_output_item_count')
+
+    def get_input_var_names(self):
+        return self._cache('get_input_var_names')
+
+    def get_output_var_names(self):
+        return self._cache('get_output_var_names')
+
+    def get_start_time(self):
+        return self._cache('get_start_time')
+
+    def get_current_time(self):
+        return self.origin.get_current_time()
+
+    def get_end_time(self):
+        return self._cache('get_end_time')
+
+    def get_time_step(self):
+        return self._cache('get_time_step')
+
+    def get_time_units(self):
+        return self._cache('get_time_units')
+
+    def get_var_type(self, var_name):
+        return self._cache('get_var_type', var_name)
+
+    def get_var_units(self, var_name):
+        return self._cache('get_var_units', var_name)
+
+    def get_var_itemsize(self, var_name):
+        return self._cache('get_var_itemsize', var_name)
+
+    def get_var_nbytes(self, var_name):
+        return self._cache('get_var_nbytes', var_name)
+
+    def get_var_location(self, var_name):
+        return self._cache('get_var_location', var_name)
+
+    def get_var_grid(self, var_name):
+        return self._cache('get_var_grid', var_name)
+
+    def get_value(self, var_name, dest):
+        return self.origin.get_value(var_name, dest)
+
+    def get_value_ptr(self, var_name):
+        return self.origin.get_value_ptr(var_name)  
+
+    def get_value_at_indices(self, var_name, dest, inds):
+        return self.origin.get_value_at_indices(var_name, dest, inds)
+
+    def set_value(self, var_name, src):
+        return self.origin.set_value(var_name, src)
+
+    def set_value_at_indices(self, var_name, indices, src):
+        return self.origin.set_value_at_indices(var_name, indices, src)
+
+    def get_grid_shape(self, grid, shape):
+        shape[:] = self._cache('get_grid_shape', grid, shape)
+        return shape
+
+    def get_grid_x(self, grid, x):
+        x[:] = self._cache('get_grid_x', grid, x)
+        return x
+
+    def get_grid_y(self, grid, y):
+        y[:] = self._cache('get_grid_y', grid, y)
+        return y
+
+    def get_grid_z(self, grid, z):
+        z[:] = self._cache('get_grid_z', grid, z)
+        return z
+
+    def get_grid_spacing(self, grid, spacing):
+        spacing[:] = self._cache('get_grid_spacing', grid, spacing)
+        return spacing
+
+    def get_grid_origin(self, grid, origin):
+        origin[:] = self._cache('get_grid_origin', grid, origin)
+        return origin
+
+    def get_grid_rank(self, grid):
+        return self._cache('get_grid_rank', grid)
+
+    def get_grid_size(self, grid):
+        return self._cache('get_grid_size', grid)
+
+    def get_grid_type(self, grid):
+        return self._cache('get_grid_type', grid)
+
+    def get_grid_node_count(self, grid):
+        return self._cache('get_grid_node_count', grid)
+
+    def get_grid_edge_count(self, grid):
+        return self._cache('get_grid_edge_count', grid)
+
+    def get_grid_face_count(self, grid):
+        return self._cache('get_grid_face_count', grid)
+
+    def get_grid_edge_nodes(self, grid, edge_nodes):
+        edge_nodes[:] = self._cache('get_grid_edge_nodes', grid, edge_nodes)
+        return edge_nodes
+
+    def get_grid_face_edges(self, grid, face_edges):
+        face_edges[:] = self._cache('get_grid_face_edges', grid, face_edges)
+        return face_edges
+
+    def get_grid_face_nodes(self, grid, face_nodes):
+        face_nodes[:] = self._cache('get_grid_face_nodes', grid, face_nodes)
+        return face_nodes
+    
+    def get_grid_nodes_per_face(self, grid, nodes_per_face):
+        nodes_per_face[:] = self._cache('get_grid_nodes_per_face', grid, nodes_per_face)
+        return nodes_per_face
diff --git a/grpc4bmi/bmi_optionaldest.py b/grpc4bmi/bmi_optionaldest.py
new file mode 100644
index 0000000..3b2283e
--- /dev/null
+++ b/grpc4bmi/bmi_optionaldest.py
@@ -0,0 +1,206 @@
+from typing import Optional, Tuple
+from bmipy import Bmi
+import numpy as np
+
+from grpc4bmi.reserve import reserve_grid_edge_nodes, reserve_grid_face_, reserve_grid_nodes, reserve_grid_padding, reserve_grid_shape, reserve_grid_nodes_per_face, reserve_values, reserve_values_at_indices
+
+class OptionalDestBmi(Bmi):
+    """Proxy around Bmi object that makes the output argument (dest) optional.
+
+    Some Bmi methods accept a numpy array as argument (dest) that must be filled by the model.
+    This class makes that argument optional.
+
+    For example to get the value of a `heat model <https://github.com/csdms/bmi-example-python>`_:
+
+    .. code-block:: python
+
+            from heat import BmiHeat
+            import numpy as np
+
+            orig_model = BmiHeat()
+            orig_model.initialize()
+            dest = np.empty((200,), dtype=np.float64)
+            dest2 = orig_model.get_value('plate_surface__temperature', dest)
+            
+
+    A `dest` variable must created and passed to the `get_value` method.
+    It must be set to a Numpy array with the shape and type the model requires.
+    The returned numpy array (`dest2`) is the same as the `dest` numpy array.
+    By using this class we no longer need do create and pass it.
+
+    .. code-block:: python
+
+        from grpc4bmi.bmi_optionaldest import OptionalDestBmi
+
+        model = OptionalDestBmi(orig_model)
+        dest3 = model.get_value('plate_surface__temperature')
+
+    This class will create dest variables by calling other Bmi methods for its shape.
+    When using a slow model it is strongly suggested to wrap the BmiClient with :class:`grpc4bmi.bmi_memoized.MemoizedBmi` first to prevent a lot of calls.
+
+    The `dest` in :external+bmipy:ref:`bmipy.Bmi.get_value_at_indices(self, name, dest, inds) <get_value_at_indices>` method
+    can not be made optional so it has been changed to
+    :func:`OptionalDestBmi.get_value_at_indices(self, name, inds) <grpc4bmi.bmi_optionaldest.OptionalDestBmi.get_value_at_indices>`.
+    """
+    def __init__(self, origin: Bmi):
+        self.origin = origin
+
+    def initialize(self, config_file: Optional[str]) -> None:
+        return self.origin.initialize(config_file)
+
+    def update(self) -> None:
+        self.origin.update()
+
+    def update_until(self, time) -> None:
+        self.origin.update_until(time)
+
+    def finalize(self) -> None:
+        self.origin.finalize()
+
+    def get_component_name(self) -> str:
+        return self.origin.get_component_name()
+
+    def get_input_item_count(self) -> int:
+        return self.origin.get_input_item_count()
+
+    def get_output_item_count(self) -> int:
+        return self.origin.get_output_item_count()
+
+    def get_input_var_names(self) -> Tuple[str]:
+        return self.origin.get_input_var_names()
+
+    def get_output_var_names(self) -> Tuple[str]:
+        return self.origin.get_output_var_names()
+
+    def get_start_time(self) -> float:
+        return self.origin.get_start_time()
+
+    def get_current_time(self) -> float:
+        return self.origin.get_current_time()
+
+    def get_end_time(self) -> float:
+        return self.origin.get_end_time()
+
+    def get_time_step(self) -> float:
+        return self.origin.get_time_step()
+
+    def get_time_units(self) -> str:
+        return self.origin.get_time_units()
+
+    def get_var_type(self, name: str) -> str:
+        return self.origin.get_var_type(name)
+
+    def get_var_units(self, name: str) -> str:
+        return self.origin.get_var_units(name)
+
+    def get_var_itemsize(self, name: str) -> int:
+        return self.origin.get_var_itemsize(name)
+
+    def get_var_nbytes(self, name: str) -> int:
+        return self.origin.get_var_nbytes(name)
+
+    def get_var_location(self, name: str) -> str:
+        return self.origin.get_var_location(name)
+
+    def get_var_grid(self, name: str) -> int:
+        return self.origin.get_var_grid(name)
+
+    def get_value(self, name, dest: Optional[np.ndarray]=None) -> np.ndarray:
+        if dest is None:
+            dest = reserve_values(self.origin, name)
+        return self.origin.get_value(name, dest)
+
+    def get_value_ptr(self, name: str) -> np.ndarray:
+        return self.origin.get_value_ptr(name)  
+
+    def get_value_at_indices(self, name: str, inds: np.ndarray) -> np.ndarray:
+        """Get values at particular indices.
+
+        Parameters
+        ----------
+        name : str
+            An input or output variable name, a CSDMS Standard Name.
+        inds : array_like
+            The indices into the variable array.
+
+        Returns
+        -------
+        array_like
+            Value of the model variable at the given location.
+        """
+        dest = reserve_values_at_indices(self.origin, name, inds)
+        return self.origin.get_value_at_indices(name, dest, inds)
+
+    def set_value(self, name: str, values: np.ndarray) -> None:
+        return self.origin.set_value(name, values)
+
+    def set_value_at_indices(self, name: str, inds: np.ndarray, src: np.ndarray) -> None:
+        return self.origin.set_value_at_indices(name, inds, src)
+
+    def get_grid_shape(self, grid: int, shape: Optional[np.ndarray]=None) -> np.ndarray:
+        if shape is None:
+            shape = reserve_grid_shape(self.origin, grid)
+        return self.origin.get_grid_shape(grid, shape)
+
+    def get_grid_x(self, grid: int, x: Optional[np.ndarray]=None) -> np.ndarray:
+        if x is None:
+            x = reserve_grid_nodes(self.origin, grid, 0)
+        return self.origin.get_grid_x(grid, x)
+
+    def get_grid_y(self, grid: int, y: Optional[np.ndarray]=None) -> np.ndarray:
+        if y is None:
+            y = reserve_grid_nodes(self.origin, grid, 1)
+        return self.origin.get_grid_y(grid, y)
+
+    def get_grid_z(self, grid: int, z: Optional[np.ndarray]=None) -> np.ndarray:
+        if z is None:
+            z = reserve_grid_nodes(self.origin, grid, 2)
+        return self.origin.get_grid_z(grid, z)
+
+    def get_grid_spacing(self, grid: int, spacing: Optional[np.ndarray]=None) -> np.ndarray:
+        if spacing is None:
+            spacing = reserve_grid_padding(self.origin, grid)
+        return self.origin.get_grid_spacing(grid, spacing)
+
+    def get_grid_origin(self, grid: int, origin: Optional[np.ndarray]=None) -> np.ndarray:
+        if origin is None:
+            origin = reserve_grid_padding(self.origin, grid)
+        return self.origin.get_grid_origin(grid, origin)
+
+    def get_grid_rank(self, grid: int) -> int:
+        return self.origin.get_grid_rank(grid)
+
+    def get_grid_size(self, grid: int) -> int:
+        return self.origin.get_grid_size(grid)
+
+    def get_grid_type(self, grid: int) -> str:
+        return self.origin.get_grid_type(grid)
+
+    def get_grid_node_count(self, grid: int) -> int:
+        return self.origin.get_grid_node_count(grid)
+
+    def get_grid_edge_count(self, grid: int) -> int:
+        return self.origin.get_grid_edge_count(grid)
+
+    def get_grid_face_count(self, grid: int) -> int:
+        return self.origin.get_grid_face_count(grid)
+
+    def get_grid_edge_nodes(self, grid: int, edge_nodes: Optional[np.ndarray]=None) -> np.ndarray:
+        if edge_nodes is None:
+            edge_nodes = reserve_grid_edge_nodes(self.origin, grid)
+        return self.origin.get_grid_edge_nodes(grid, edge_nodes)
+
+    def get_grid_face_edges(self, grid: int, face_edges: Optional[np.ndarray]=None) -> np.ndarray:
+        if face_edges is None:
+            face_edges = reserve_grid_face_(self.origin, grid)
+        return self.origin.get_grid_face_edges(grid, face_edges)
+
+    def get_grid_face_nodes(self, grid: int, face_nodes: Optional[np.ndarray]=None) -> np.ndarray:
+        if face_nodes is None:
+            face_nodes = reserve_grid_face_(self.origin, grid)
+        return self.origin.get_grid_face_nodes(grid, face_nodes)
+    
+    def get_grid_nodes_per_face(self, grid: int, nodes_per_face: Optional[np.ndarray]=None) -> np.ndarray:
+        if nodes_per_face is None:
+            nodes_per_face = reserve_grid_nodes_per_face(self.origin, grid)
+        return self.origin.get_grid_nodes_per_face(grid, nodes_per_face)
diff --git a/grpc4bmi/bmi_pb2.py b/grpc4bmi/bmi_pb2.py
index 6b6f9c4..da2f84c 100644
--- a/grpc4bmi/bmi_pb2.py
+++ b/grpc4bmi/bmi_pb2.py
@@ -1,10 +1,10 @@
 # -*- coding: utf-8 -*-
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: grpc4bmi/bmi.proto
-
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
 from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
+from google.protobuf import descriptor_pool as _descriptor_pool
 from google.protobuf import symbol_database as _symbol_database
 # @@protoc_insertion_point(imports)
 
@@ -13,1982 +13,113 @@
 
 
 
-DESCRIPTOR = _descriptor.FileDescriptor(
-  name='grpc4bmi/bmi.proto',
-  package='bmi',
-  syntax='proto3',
-  serialized_options=None,
-  serialized_pb=b'\n\x12grpc4bmi/bmi.proto\x12\x03\x62mi\"\x07\n\x05\x45mpty\"(\n\x11InitializeRequest\x12\x13\n\x0b\x63onfig_file\x18\x01 \x01(\t\"(\n\x18GetComponentNameResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\"$\n\x13GetVarNamesResponse\x12\r\n\x05names\x18\x01 \x03(\t\"%\n\x14GetTimeUnitsResponse\x12\r\n\x05units\x18\x01 \x01(\t\"\'\n\x13GetTimeStepResponse\x12\x10\n\x08interval\x18\x01 \x01(\x01\"\x1f\n\x0fGetTimeResponse\x12\x0c\n\x04time\x18\x01 \x01(\x01\"\x1d\n\rGetVarRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12GetVarGridResponse\x12\x0f\n\x07grid_id\x18\x01 \x01(\x05\"\"\n\x12GetVarTypeResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\"&\n\x16GetVarItemSizeResponse\x12\x0c\n\x04size\x18\x01 \x01(\x03\"$\n\x13GetVarUnitsResponse\x12\r\n\x05units\x18\x01 \x01(\t\"&\n\x14GetVarNBytesResponse\x12\x0e\n\x06nbytes\x18\x01 \x01(\x03\"z\n\x16GetVarLocationResponse\x12\x36\n\x08location\x18\x01 \x01(\x0e\x32$.bmi.GetVarLocationResponse.Location\"(\n\x08Location\x12\x08\n\x04NODE\x10\x00\x12\x08\n\x04\x45\x44GE\x10\x01\x12\x08\n\x04\x46\x41\x43\x45\x10\x02\"%\n\x0fIntArrayMessage\x12\x12\n\x06values\x18\x01 \x03(\x03\x42\x02\x10\x01\"\'\n\x11\x46loatArrayMessage\x12\x12\n\x06values\x18\x01 \x03(\x02\x42\x02\x10\x01\"(\n\x12\x44oubleArrayMessage\x12\x12\n\x06values\x18\x01 \x03(\x01\x42\x02\x10\x01\"\xaa\x01\n\x10GetValueResponse\x12*\n\nvalues_int\x18\x01 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x02 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x03 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"=\n\x18GetValueAtIndicesRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x07indices\x18\x02 \x03(\x03\x42\x02\x10\x01\"\xb3\x01\n\x19GetValueAtIndicesResponse\x12*\n\nvalues_int\x18\x01 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x02 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x03 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"\xb7\x01\n\x0fSetValueRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12*\n\nvalues_int\x18\x02 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x03 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x04 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"/\n\x12SetValuePtrRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03ref\x18\x02 \x01(\x03\"\xd5\x01\n\x18SetValueAtIndicesRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x07indices\x18\x02 \x03(\x03\x42\x02\x10\x01\x12*\n\nvalues_int\x18\x03 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x04 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x05 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"\x1e\n\x0bGridRequest\x12\x0f\n\x07grid_id\x18\x01 \x01(\x03\"#\n\x13GetGridSizeResponse\x12\x0c\n\x04size\x18\x01 \x01(\x03\"#\n\x13GetGridRankResponse\x12\x0c\n\x04rank\x18\x01 \x01(\x03\"#\n\x13GetGridTypeResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\")\n\x14GetGridShapeResponse\x12\x11\n\x05shape\x18\x01 \x03(\x03\x42\x02\x10\x01\"-\n\x16GetGridSpacingResponse\x12\x13\n\x07spacing\x18\x01 \x03(\x01\x42\x02\x10\x01\"+\n\x15GetGridOriginResponse\x12\x12\n\x06origin\x18\x01 \x03(\x01\x42\x02\x10\x01\"0\n\x15GetGridPointsResponse\x12\x17\n\x0b\x63oordinates\x18\x01 \x03(\x01\x42\x02\x10\x01\"!\n\x10GetCountResponse\x12\r\n\x05\x63ount\x18\x01 \x01(\x03\"2\n\x18GetGridEdgeNodesResponse\x12\x16\n\nedge_nodes\x18\x01 \x03(\x03\x42\x02\x10\x01\"2\n\x18GetGridFaceEdgesResponse\x12\x16\n\nface_edges\x18\x01 \x03(\x03\x42\x02\x10\x01\"2\n\x18GetGridFaceNodesResponse\x12\x16\n\nface_nodes\x18\x01 \x03(\x03\x42\x02\x10\x01\"9\n\x1bGetGridNodesPerFaceResponse\x12\x1a\n\x0enodes_per_face\x18\x01 \x03(\x03\x42\x02\x10\x01\x32\x8a\x13\n\nBmiService\x12\x32\n\ninitialize\x12\x16.bmi.InitializeRequest\x1a\n.bmi.Empty\"\x00\x12\"\n\x06update\x12\n.bmi.Empty\x1a\n.bmi.Empty\"\x00\x12\x31\n\x0bupdateUntil\x12\x14.bmi.GetTimeResponse\x1a\n.bmi.Empty\"\x00\x12$\n\x08\x66inalize\x12\n.bmi.Empty\x1a\n.bmi.Empty\"\x00\x12?\n\x10getComponentName\x12\n.bmi.Empty\x1a\x1d.bmi.GetComponentNameResponse\"\x00\x12\x38\n\x11getInputItemCount\x12\n.bmi.Empty\x1a\x15.bmi.GetCountResponse\"\x00\x12\x39\n\x12getOutputItemCount\x12\n.bmi.Empty\x1a\x15.bmi.GetCountResponse\"\x00\x12:\n\x10getInputVarNames\x12\n.bmi.Empty\x1a\x18.bmi.GetVarNamesResponse\"\x00\x12;\n\x11getOutputVarNames\x12\n.bmi.Empty\x1a\x18.bmi.GetVarNamesResponse\"\x00\x12\x37\n\x0cgetTimeUnits\x12\n.bmi.Empty\x1a\x19.bmi.GetTimeUnitsResponse\"\x00\x12\x35\n\x0bgetTimeStep\x12\n.bmi.Empty\x1a\x18.bmi.GetTimeStepResponse\"\x00\x12\x34\n\x0egetCurrentTime\x12\n.bmi.Empty\x1a\x14.bmi.GetTimeResponse\"\x00\x12\x32\n\x0cgetStartTime\x12\n.bmi.Empty\x1a\x14.bmi.GetTimeResponse\"\x00\x12\x30\n\ngetEndTime\x12\n.bmi.Empty\x1a\x14.bmi.GetTimeResponse\"\x00\x12;\n\ngetVarGrid\x12\x12.bmi.GetVarRequest\x1a\x17.bmi.GetVarGridResponse\"\x00\x12;\n\ngetVarType\x12\x12.bmi.GetVarRequest\x1a\x17.bmi.GetVarTypeResponse\"\x00\x12\x43\n\x0egetVarItemSize\x12\x12.bmi.GetVarRequest\x1a\x1b.bmi.GetVarItemSizeResponse\"\x00\x12=\n\x0bgetVarUnits\x12\x12.bmi.GetVarRequest\x1a\x18.bmi.GetVarUnitsResponse\"\x00\x12?\n\x0cgetVarNBytes\x12\x12.bmi.GetVarRequest\x1a\x19.bmi.GetVarNBytesResponse\"\x00\x12\x43\n\x0egetVarLocation\x12\x12.bmi.GetVarRequest\x1a\x1b.bmi.GetVarLocationResponse\"\x00\x12\x37\n\x08getValue\x12\x12.bmi.GetVarRequest\x1a\x15.bmi.GetValueResponse\"\x00\x12T\n\x11getValueAtIndices\x12\x1d.bmi.GetValueAtIndicesRequest\x1a\x1e.bmi.GetValueAtIndicesResponse\"\x00\x12.\n\x08setValue\x12\x14.bmi.SetValueRequest\x1a\n.bmi.Empty\"\x00\x12@\n\x11setValueAtIndices\x12\x1d.bmi.SetValueAtIndicesRequest\x1a\n.bmi.Empty\"\x00\x12;\n\x0bgetGridSize\x12\x10.bmi.GridRequest\x1a\x18.bmi.GetGridSizeResponse\"\x00\x12;\n\x0bgetGridType\x12\x10.bmi.GridRequest\x1a\x18.bmi.GetGridTypeResponse\"\x00\x12;\n\x0bgetGridRank\x12\x10.bmi.GridRequest\x1a\x18.bmi.GetGridRankResponse\"\x00\x12=\n\x0cgetGridShape\x12\x10.bmi.GridRequest\x1a\x19.bmi.GetGridShapeResponse\"\x00\x12\x41\n\x0egetGridSpacing\x12\x10.bmi.GridRequest\x1a\x1b.bmi.GetGridSpacingResponse\"\x00\x12?\n\rgetGridOrigin\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridOriginResponse\"\x00\x12:\n\x08getGridX\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridPointsResponse\"\x00\x12:\n\x08getGridY\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridPointsResponse\"\x00\x12:\n\x08getGridZ\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridPointsResponse\"\x00\x12=\n\x10getGridNodeCount\x12\x10.bmi.GridRequest\x1a\x15.bmi.GetCountResponse\"\x00\x12=\n\x10getGridEdgeCount\x12\x10.bmi.GridRequest\x1a\x15.bmi.GetCountResponse\"\x00\x12=\n\x10getGridFaceCount\x12\x10.bmi.GridRequest\x1a\x15.bmi.GetCountResponse\"\x00\x12\x45\n\x10getGridEdgeNodes\x12\x10.bmi.GridRequest\x1a\x1d.bmi.GetGridEdgeNodesResponse\"\x00\x12\x45\n\x10getGridFaceNodes\x12\x10.bmi.GridRequest\x1a\x1d.bmi.GetGridFaceNodesResponse\"\x00\x12\x45\n\x10getGridFaceEdges\x12\x10.bmi.GridRequest\x1a\x1d.bmi.GetGridFaceEdgesResponse\"\x00\x12K\n\x13getGridNodesPerFace\x12\x10.bmi.GridRequest\x1a .bmi.GetGridNodesPerFaceResponse\"\x00\x62\x06proto3'
-)
-
-
-
-_GETVARLOCATIONRESPONSE_LOCATION = _descriptor.EnumDescriptor(
-  name='Location',
-  full_name='bmi.GetVarLocationResponse.Location',
-  filename=None,
-  file=DESCRIPTOR,
-  values=[
-    _descriptor.EnumValueDescriptor(
-      name='NODE', index=0, number=0,
-      serialized_options=None,
-      type=None),
-    _descriptor.EnumValueDescriptor(
-      name='EDGE', index=1, number=1,
-      serialized_options=None,
-      type=None),
-    _descriptor.EnumValueDescriptor(
-      name='FACE', index=2, number=2,
-      serialized_options=None,
-      type=None),
-  ],
-  containing_type=None,
-  serialized_options=None,
-  serialized_start=577,
-  serialized_end=617,
-)
-_sym_db.RegisterEnumDescriptor(_GETVARLOCATIONRESPONSE_LOCATION)
-
-
-_EMPTY = _descriptor.Descriptor(
-  name='Empty',
-  full_name='bmi.Empty',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=27,
-  serialized_end=34,
-)
-
-
-_INITIALIZEREQUEST = _descriptor.Descriptor(
-  name='InitializeRequest',
-  full_name='bmi.InitializeRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='config_file', full_name='bmi.InitializeRequest.config_file', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=36,
-  serialized_end=76,
-)
-
-
-_GETCOMPONENTNAMERESPONSE = _descriptor.Descriptor(
-  name='GetComponentNameResponse',
-  full_name='bmi.GetComponentNameResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='name', full_name='bmi.GetComponentNameResponse.name', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=78,
-  serialized_end=118,
-)
-
-
-_GETVARNAMESRESPONSE = _descriptor.Descriptor(
-  name='GetVarNamesResponse',
-  full_name='bmi.GetVarNamesResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='names', full_name='bmi.GetVarNamesResponse.names', index=0,
-      number=1, type=9, cpp_type=9, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=120,
-  serialized_end=156,
-)
-
-
-_GETTIMEUNITSRESPONSE = _descriptor.Descriptor(
-  name='GetTimeUnitsResponse',
-  full_name='bmi.GetTimeUnitsResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='units', full_name='bmi.GetTimeUnitsResponse.units', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=158,
-  serialized_end=195,
-)
-
-
-_GETTIMESTEPRESPONSE = _descriptor.Descriptor(
-  name='GetTimeStepResponse',
-  full_name='bmi.GetTimeStepResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='interval', full_name='bmi.GetTimeStepResponse.interval', index=0,
-      number=1, type=1, cpp_type=5, label=1,
-      has_default_value=False, default_value=float(0),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=197,
-  serialized_end=236,
-)
-
-
-_GETTIMERESPONSE = _descriptor.Descriptor(
-  name='GetTimeResponse',
-  full_name='bmi.GetTimeResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='time', full_name='bmi.GetTimeResponse.time', index=0,
-      number=1, type=1, cpp_type=5, label=1,
-      has_default_value=False, default_value=float(0),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=238,
-  serialized_end=269,
-)
-
-
-_GETVARREQUEST = _descriptor.Descriptor(
-  name='GetVarRequest',
-  full_name='bmi.GetVarRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='name', full_name='bmi.GetVarRequest.name', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=271,
-  serialized_end=300,
-)
-
-
-_GETVARGRIDRESPONSE = _descriptor.Descriptor(
-  name='GetVarGridResponse',
-  full_name='bmi.GetVarGridResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='grid_id', full_name='bmi.GetVarGridResponse.grid_id', index=0,
-      number=1, type=5, cpp_type=1, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=302,
-  serialized_end=339,
-)
-
-
-_GETVARTYPERESPONSE = _descriptor.Descriptor(
-  name='GetVarTypeResponse',
-  full_name='bmi.GetVarTypeResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='type', full_name='bmi.GetVarTypeResponse.type', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=341,
-  serialized_end=375,
-)
-
-
-_GETVARITEMSIZERESPONSE = _descriptor.Descriptor(
-  name='GetVarItemSizeResponse',
-  full_name='bmi.GetVarItemSizeResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='size', full_name='bmi.GetVarItemSizeResponse.size', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=377,
-  serialized_end=415,
-)
-
-
-_GETVARUNITSRESPONSE = _descriptor.Descriptor(
-  name='GetVarUnitsResponse',
-  full_name='bmi.GetVarUnitsResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='units', full_name='bmi.GetVarUnitsResponse.units', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=417,
-  serialized_end=453,
-)
-
-
-_GETVARNBYTESRESPONSE = _descriptor.Descriptor(
-  name='GetVarNBytesResponse',
-  full_name='bmi.GetVarNBytesResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='nbytes', full_name='bmi.GetVarNBytesResponse.nbytes', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=455,
-  serialized_end=493,
-)
-
-
-_GETVARLOCATIONRESPONSE = _descriptor.Descriptor(
-  name='GetVarLocationResponse',
-  full_name='bmi.GetVarLocationResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='location', full_name='bmi.GetVarLocationResponse.location', index=0,
-      number=1, type=14, cpp_type=8, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-    _GETVARLOCATIONRESPONSE_LOCATION,
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=495,
-  serialized_end=617,
-)
-
-
-_INTARRAYMESSAGE = _descriptor.Descriptor(
-  name='IntArrayMessage',
-  full_name='bmi.IntArrayMessage',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='values', full_name='bmi.IntArrayMessage.values', index=0,
-      number=1, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=619,
-  serialized_end=656,
-)
-
-
-_FLOATARRAYMESSAGE = _descriptor.Descriptor(
-  name='FloatArrayMessage',
-  full_name='bmi.FloatArrayMessage',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='values', full_name='bmi.FloatArrayMessage.values', index=0,
-      number=1, type=2, cpp_type=6, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=658,
-  serialized_end=697,
-)
-
-
-_DOUBLEARRAYMESSAGE = _descriptor.Descriptor(
-  name='DoubleArrayMessage',
-  full_name='bmi.DoubleArrayMessage',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='values', full_name='bmi.DoubleArrayMessage.values', index=0,
-      number=1, type=1, cpp_type=5, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=699,
-  serialized_end=739,
-)
-
-
-_GETVALUERESPONSE = _descriptor.Descriptor(
-  name='GetValueResponse',
-  full_name='bmi.GetValueResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='values_int', full_name='bmi.GetValueResponse.values_int', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_float', full_name='bmi.GetValueResponse.values_float', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_double', full_name='bmi.GetValueResponse.values_double', index=2,
-      number=3, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-    _descriptor.OneofDescriptor(
-      name='values', full_name='bmi.GetValueResponse.values',
-      index=0, containing_type=None, fields=[]),
-  ],
-  serialized_start=742,
-  serialized_end=912,
-)
-
-
-_GETVALUEATINDICESREQUEST = _descriptor.Descriptor(
-  name='GetValueAtIndicesRequest',
-  full_name='bmi.GetValueAtIndicesRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='name', full_name='bmi.GetValueAtIndicesRequest.name', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='indices', full_name='bmi.GetValueAtIndicesRequest.indices', index=1,
-      number=2, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=914,
-  serialized_end=975,
-)
-
-
-_GETVALUEATINDICESRESPONSE = _descriptor.Descriptor(
-  name='GetValueAtIndicesResponse',
-  full_name='bmi.GetValueAtIndicesResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='values_int', full_name='bmi.GetValueAtIndicesResponse.values_int', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_float', full_name='bmi.GetValueAtIndicesResponse.values_float', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_double', full_name='bmi.GetValueAtIndicesResponse.values_double', index=2,
-      number=3, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-    _descriptor.OneofDescriptor(
-      name='values', full_name='bmi.GetValueAtIndicesResponse.values',
-      index=0, containing_type=None, fields=[]),
-  ],
-  serialized_start=978,
-  serialized_end=1157,
-)
-
-
-_SETVALUEREQUEST = _descriptor.Descriptor(
-  name='SetValueRequest',
-  full_name='bmi.SetValueRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='name', full_name='bmi.SetValueRequest.name', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_int', full_name='bmi.SetValueRequest.values_int', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_float', full_name='bmi.SetValueRequest.values_float', index=2,
-      number=3, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_double', full_name='bmi.SetValueRequest.values_double', index=3,
-      number=4, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-    _descriptor.OneofDescriptor(
-      name='values', full_name='bmi.SetValueRequest.values',
-      index=0, containing_type=None, fields=[]),
-  ],
-  serialized_start=1160,
-  serialized_end=1343,
-)
-
-
-_SETVALUEPTRREQUEST = _descriptor.Descriptor(
-  name='SetValuePtrRequest',
-  full_name='bmi.SetValuePtrRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='name', full_name='bmi.SetValuePtrRequest.name', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='ref', full_name='bmi.SetValuePtrRequest.ref', index=1,
-      number=2, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1345,
-  serialized_end=1392,
-)
-
-
-_SETVALUEATINDICESREQUEST = _descriptor.Descriptor(
-  name='SetValueAtIndicesRequest',
-  full_name='bmi.SetValueAtIndicesRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='name', full_name='bmi.SetValueAtIndicesRequest.name', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='indices', full_name='bmi.SetValueAtIndicesRequest.indices', index=1,
-      number=2, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_int', full_name='bmi.SetValueAtIndicesRequest.values_int', index=2,
-      number=3, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_float', full_name='bmi.SetValueAtIndicesRequest.values_float', index=3,
-      number=4, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='values_double', full_name='bmi.SetValueAtIndicesRequest.values_double', index=4,
-      number=5, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-    _descriptor.OneofDescriptor(
-      name='values', full_name='bmi.SetValueAtIndicesRequest.values',
-      index=0, containing_type=None, fields=[]),
-  ],
-  serialized_start=1395,
-  serialized_end=1608,
-)
-
-
-_GRIDREQUEST = _descriptor.Descriptor(
-  name='GridRequest',
-  full_name='bmi.GridRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='grid_id', full_name='bmi.GridRequest.grid_id', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1610,
-  serialized_end=1640,
-)
-
-
-_GETGRIDSIZERESPONSE = _descriptor.Descriptor(
-  name='GetGridSizeResponse',
-  full_name='bmi.GetGridSizeResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='size', full_name='bmi.GetGridSizeResponse.size', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1642,
-  serialized_end=1677,
-)
-
-
-_GETGRIDRANKRESPONSE = _descriptor.Descriptor(
-  name='GetGridRankResponse',
-  full_name='bmi.GetGridRankResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='rank', full_name='bmi.GetGridRankResponse.rank', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1679,
-  serialized_end=1714,
-)
-
-
-_GETGRIDTYPERESPONSE = _descriptor.Descriptor(
-  name='GetGridTypeResponse',
-  full_name='bmi.GetGridTypeResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='type', full_name='bmi.GetGridTypeResponse.type', index=0,
-      number=1, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=b"".decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1716,
-  serialized_end=1751,
-)
-
-
-_GETGRIDSHAPERESPONSE = _descriptor.Descriptor(
-  name='GetGridShapeResponse',
-  full_name='bmi.GetGridShapeResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='shape', full_name='bmi.GetGridShapeResponse.shape', index=0,
-      number=1, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1753,
-  serialized_end=1794,
-)
-
-
-_GETGRIDSPACINGRESPONSE = _descriptor.Descriptor(
-  name='GetGridSpacingResponse',
-  full_name='bmi.GetGridSpacingResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='spacing', full_name='bmi.GetGridSpacingResponse.spacing', index=0,
-      number=1, type=1, cpp_type=5, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1796,
-  serialized_end=1841,
-)
-
-
-_GETGRIDORIGINRESPONSE = _descriptor.Descriptor(
-  name='GetGridOriginResponse',
-  full_name='bmi.GetGridOriginResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='origin', full_name='bmi.GetGridOriginResponse.origin', index=0,
-      number=1, type=1, cpp_type=5, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1843,
-  serialized_end=1886,
-)
-
-
-_GETGRIDPOINTSRESPONSE = _descriptor.Descriptor(
-  name='GetGridPointsResponse',
-  full_name='bmi.GetGridPointsResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='coordinates', full_name='bmi.GetGridPointsResponse.coordinates', index=0,
-      number=1, type=1, cpp_type=5, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1888,
-  serialized_end=1936,
-)
-
-
-_GETCOUNTRESPONSE = _descriptor.Descriptor(
-  name='GetCountResponse',
-  full_name='bmi.GetCountResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='count', full_name='bmi.GetCountResponse.count', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1938,
-  serialized_end=1971,
-)
-
-
-_GETGRIDEDGENODESRESPONSE = _descriptor.Descriptor(
-  name='GetGridEdgeNodesResponse',
-  full_name='bmi.GetGridEdgeNodesResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='edge_nodes', full_name='bmi.GetGridEdgeNodesResponse.edge_nodes', index=0,
-      number=1, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1973,
-  serialized_end=2023,
-)
-
-
-_GETGRIDFACEEDGESRESPONSE = _descriptor.Descriptor(
-  name='GetGridFaceEdgesResponse',
-  full_name='bmi.GetGridFaceEdgesResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='face_edges', full_name='bmi.GetGridFaceEdgesResponse.face_edges', index=0,
-      number=1, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=2025,
-  serialized_end=2075,
-)
-
-
-_GETGRIDFACENODESRESPONSE = _descriptor.Descriptor(
-  name='GetGridFaceNodesResponse',
-  full_name='bmi.GetGridFaceNodesResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='face_nodes', full_name='bmi.GetGridFaceNodesResponse.face_nodes', index=0,
-      number=1, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=2077,
-  serialized_end=2127,
-)
-
-
-_GETGRIDNODESPERFACERESPONSE = _descriptor.Descriptor(
-  name='GetGridNodesPerFaceResponse',
-  full_name='bmi.GetGridNodesPerFaceResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='nodes_per_face', full_name='bmi.GetGridNodesPerFaceResponse.nodes_per_face', index=0,
-      number=1, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=b'\020\001', file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=2129,
-  serialized_end=2186,
-)
-
-_GETVARLOCATIONRESPONSE.fields_by_name['location'].enum_type = _GETVARLOCATIONRESPONSE_LOCATION
-_GETVARLOCATIONRESPONSE_LOCATION.containing_type = _GETVARLOCATIONRESPONSE
-_GETVALUERESPONSE.fields_by_name['values_int'].message_type = _INTARRAYMESSAGE
-_GETVALUERESPONSE.fields_by_name['values_float'].message_type = _FLOATARRAYMESSAGE
-_GETVALUERESPONSE.fields_by_name['values_double'].message_type = _DOUBLEARRAYMESSAGE
-_GETVALUERESPONSE.oneofs_by_name['values'].fields.append(
-  _GETVALUERESPONSE.fields_by_name['values_int'])
-_GETVALUERESPONSE.fields_by_name['values_int'].containing_oneof = _GETVALUERESPONSE.oneofs_by_name['values']
-_GETVALUERESPONSE.oneofs_by_name['values'].fields.append(
-  _GETVALUERESPONSE.fields_by_name['values_float'])
-_GETVALUERESPONSE.fields_by_name['values_float'].containing_oneof = _GETVALUERESPONSE.oneofs_by_name['values']
-_GETVALUERESPONSE.oneofs_by_name['values'].fields.append(
-  _GETVALUERESPONSE.fields_by_name['values_double'])
-_GETVALUERESPONSE.fields_by_name['values_double'].containing_oneof = _GETVALUERESPONSE.oneofs_by_name['values']
-_GETVALUEATINDICESRESPONSE.fields_by_name['values_int'].message_type = _INTARRAYMESSAGE
-_GETVALUEATINDICESRESPONSE.fields_by_name['values_float'].message_type = _FLOATARRAYMESSAGE
-_GETVALUEATINDICESRESPONSE.fields_by_name['values_double'].message_type = _DOUBLEARRAYMESSAGE
-_GETVALUEATINDICESRESPONSE.oneofs_by_name['values'].fields.append(
-  _GETVALUEATINDICESRESPONSE.fields_by_name['values_int'])
-_GETVALUEATINDICESRESPONSE.fields_by_name['values_int'].containing_oneof = _GETVALUEATINDICESRESPONSE.oneofs_by_name['values']
-_GETVALUEATINDICESRESPONSE.oneofs_by_name['values'].fields.append(
-  _GETVALUEATINDICESRESPONSE.fields_by_name['values_float'])
-_GETVALUEATINDICESRESPONSE.fields_by_name['values_float'].containing_oneof = _GETVALUEATINDICESRESPONSE.oneofs_by_name['values']
-_GETVALUEATINDICESRESPONSE.oneofs_by_name['values'].fields.append(
-  _GETVALUEATINDICESRESPONSE.fields_by_name['values_double'])
-_GETVALUEATINDICESRESPONSE.fields_by_name['values_double'].containing_oneof = _GETVALUEATINDICESRESPONSE.oneofs_by_name['values']
-_SETVALUEREQUEST.fields_by_name['values_int'].message_type = _INTARRAYMESSAGE
-_SETVALUEREQUEST.fields_by_name['values_float'].message_type = _FLOATARRAYMESSAGE
-_SETVALUEREQUEST.fields_by_name['values_double'].message_type = _DOUBLEARRAYMESSAGE
-_SETVALUEREQUEST.oneofs_by_name['values'].fields.append(
-  _SETVALUEREQUEST.fields_by_name['values_int'])
-_SETVALUEREQUEST.fields_by_name['values_int'].containing_oneof = _SETVALUEREQUEST.oneofs_by_name['values']
-_SETVALUEREQUEST.oneofs_by_name['values'].fields.append(
-  _SETVALUEREQUEST.fields_by_name['values_float'])
-_SETVALUEREQUEST.fields_by_name['values_float'].containing_oneof = _SETVALUEREQUEST.oneofs_by_name['values']
-_SETVALUEREQUEST.oneofs_by_name['values'].fields.append(
-  _SETVALUEREQUEST.fields_by_name['values_double'])
-_SETVALUEREQUEST.fields_by_name['values_double'].containing_oneof = _SETVALUEREQUEST.oneofs_by_name['values']
-_SETVALUEATINDICESREQUEST.fields_by_name['values_int'].message_type = _INTARRAYMESSAGE
-_SETVALUEATINDICESREQUEST.fields_by_name['values_float'].message_type = _FLOATARRAYMESSAGE
-_SETVALUEATINDICESREQUEST.fields_by_name['values_double'].message_type = _DOUBLEARRAYMESSAGE
-_SETVALUEATINDICESREQUEST.oneofs_by_name['values'].fields.append(
-  _SETVALUEATINDICESREQUEST.fields_by_name['values_int'])
-_SETVALUEATINDICESREQUEST.fields_by_name['values_int'].containing_oneof = _SETVALUEATINDICESREQUEST.oneofs_by_name['values']
-_SETVALUEATINDICESREQUEST.oneofs_by_name['values'].fields.append(
-  _SETVALUEATINDICESREQUEST.fields_by_name['values_float'])
-_SETVALUEATINDICESREQUEST.fields_by_name['values_float'].containing_oneof = _SETVALUEATINDICESREQUEST.oneofs_by_name['values']
-_SETVALUEATINDICESREQUEST.oneofs_by_name['values'].fields.append(
-  _SETVALUEATINDICESREQUEST.fields_by_name['values_double'])
-_SETVALUEATINDICESREQUEST.fields_by_name['values_double'].containing_oneof = _SETVALUEATINDICESREQUEST.oneofs_by_name['values']
-DESCRIPTOR.message_types_by_name['Empty'] = _EMPTY
-DESCRIPTOR.message_types_by_name['InitializeRequest'] = _INITIALIZEREQUEST
-DESCRIPTOR.message_types_by_name['GetComponentNameResponse'] = _GETCOMPONENTNAMERESPONSE
-DESCRIPTOR.message_types_by_name['GetVarNamesResponse'] = _GETVARNAMESRESPONSE
-DESCRIPTOR.message_types_by_name['GetTimeUnitsResponse'] = _GETTIMEUNITSRESPONSE
-DESCRIPTOR.message_types_by_name['GetTimeStepResponse'] = _GETTIMESTEPRESPONSE
-DESCRIPTOR.message_types_by_name['GetTimeResponse'] = _GETTIMERESPONSE
-DESCRIPTOR.message_types_by_name['GetVarRequest'] = _GETVARREQUEST
-DESCRIPTOR.message_types_by_name['GetVarGridResponse'] = _GETVARGRIDRESPONSE
-DESCRIPTOR.message_types_by_name['GetVarTypeResponse'] = _GETVARTYPERESPONSE
-DESCRIPTOR.message_types_by_name['GetVarItemSizeResponse'] = _GETVARITEMSIZERESPONSE
-DESCRIPTOR.message_types_by_name['GetVarUnitsResponse'] = _GETVARUNITSRESPONSE
-DESCRIPTOR.message_types_by_name['GetVarNBytesResponse'] = _GETVARNBYTESRESPONSE
-DESCRIPTOR.message_types_by_name['GetVarLocationResponse'] = _GETVARLOCATIONRESPONSE
-DESCRIPTOR.message_types_by_name['IntArrayMessage'] = _INTARRAYMESSAGE
-DESCRIPTOR.message_types_by_name['FloatArrayMessage'] = _FLOATARRAYMESSAGE
-DESCRIPTOR.message_types_by_name['DoubleArrayMessage'] = _DOUBLEARRAYMESSAGE
-DESCRIPTOR.message_types_by_name['GetValueResponse'] = _GETVALUERESPONSE
-DESCRIPTOR.message_types_by_name['GetValueAtIndicesRequest'] = _GETVALUEATINDICESREQUEST
-DESCRIPTOR.message_types_by_name['GetValueAtIndicesResponse'] = _GETVALUEATINDICESRESPONSE
-DESCRIPTOR.message_types_by_name['SetValueRequest'] = _SETVALUEREQUEST
-DESCRIPTOR.message_types_by_name['SetValuePtrRequest'] = _SETVALUEPTRREQUEST
-DESCRIPTOR.message_types_by_name['SetValueAtIndicesRequest'] = _SETVALUEATINDICESREQUEST
-DESCRIPTOR.message_types_by_name['GridRequest'] = _GRIDREQUEST
-DESCRIPTOR.message_types_by_name['GetGridSizeResponse'] = _GETGRIDSIZERESPONSE
-DESCRIPTOR.message_types_by_name['GetGridRankResponse'] = _GETGRIDRANKRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridTypeResponse'] = _GETGRIDTYPERESPONSE
-DESCRIPTOR.message_types_by_name['GetGridShapeResponse'] = _GETGRIDSHAPERESPONSE
-DESCRIPTOR.message_types_by_name['GetGridSpacingResponse'] = _GETGRIDSPACINGRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridOriginResponse'] = _GETGRIDORIGINRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridPointsResponse'] = _GETGRIDPOINTSRESPONSE
-DESCRIPTOR.message_types_by_name['GetCountResponse'] = _GETCOUNTRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridEdgeNodesResponse'] = _GETGRIDEDGENODESRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridFaceEdgesResponse'] = _GETGRIDFACEEDGESRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridFaceNodesResponse'] = _GETGRIDFACENODESRESPONSE
-DESCRIPTOR.message_types_by_name['GetGridNodesPerFaceResponse'] = _GETGRIDNODESPERFACERESPONSE
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), {
-  'DESCRIPTOR' : _EMPTY,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.Empty)
-  })
-_sym_db.RegisterMessage(Empty)
-
-InitializeRequest = _reflection.GeneratedProtocolMessageType('InitializeRequest', (_message.Message,), {
-  'DESCRIPTOR' : _INITIALIZEREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.InitializeRequest)
-  })
-_sym_db.RegisterMessage(InitializeRequest)
-
-GetComponentNameResponse = _reflection.GeneratedProtocolMessageType('GetComponentNameResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETCOMPONENTNAMERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetComponentNameResponse)
-  })
-_sym_db.RegisterMessage(GetComponentNameResponse)
-
-GetVarNamesResponse = _reflection.GeneratedProtocolMessageType('GetVarNamesResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARNAMESRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarNamesResponse)
-  })
-_sym_db.RegisterMessage(GetVarNamesResponse)
-
-GetTimeUnitsResponse = _reflection.GeneratedProtocolMessageType('GetTimeUnitsResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETTIMEUNITSRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetTimeUnitsResponse)
-  })
-_sym_db.RegisterMessage(GetTimeUnitsResponse)
-
-GetTimeStepResponse = _reflection.GeneratedProtocolMessageType('GetTimeStepResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETTIMESTEPRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetTimeStepResponse)
-  })
-_sym_db.RegisterMessage(GetTimeStepResponse)
-
-GetTimeResponse = _reflection.GeneratedProtocolMessageType('GetTimeResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETTIMERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetTimeResponse)
-  })
-_sym_db.RegisterMessage(GetTimeResponse)
-
-GetVarRequest = _reflection.GeneratedProtocolMessageType('GetVarRequest', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarRequest)
-  })
-_sym_db.RegisterMessage(GetVarRequest)
-
-GetVarGridResponse = _reflection.GeneratedProtocolMessageType('GetVarGridResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARGRIDRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarGridResponse)
-  })
-_sym_db.RegisterMessage(GetVarGridResponse)
-
-GetVarTypeResponse = _reflection.GeneratedProtocolMessageType('GetVarTypeResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARTYPERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarTypeResponse)
-  })
-_sym_db.RegisterMessage(GetVarTypeResponse)
-
-GetVarItemSizeResponse = _reflection.GeneratedProtocolMessageType('GetVarItemSizeResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARITEMSIZERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarItemSizeResponse)
-  })
-_sym_db.RegisterMessage(GetVarItemSizeResponse)
-
-GetVarUnitsResponse = _reflection.GeneratedProtocolMessageType('GetVarUnitsResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARUNITSRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarUnitsResponse)
-  })
-_sym_db.RegisterMessage(GetVarUnitsResponse)
-
-GetVarNBytesResponse = _reflection.GeneratedProtocolMessageType('GetVarNBytesResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARNBYTESRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarNBytesResponse)
-  })
-_sym_db.RegisterMessage(GetVarNBytesResponse)
-
-GetVarLocationResponse = _reflection.GeneratedProtocolMessageType('GetVarLocationResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVARLOCATIONRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetVarLocationResponse)
-  })
-_sym_db.RegisterMessage(GetVarLocationResponse)
-
-IntArrayMessage = _reflection.GeneratedProtocolMessageType('IntArrayMessage', (_message.Message,), {
-  'DESCRIPTOR' : _INTARRAYMESSAGE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.IntArrayMessage)
-  })
-_sym_db.RegisterMessage(IntArrayMessage)
-
-FloatArrayMessage = _reflection.GeneratedProtocolMessageType('FloatArrayMessage', (_message.Message,), {
-  'DESCRIPTOR' : _FLOATARRAYMESSAGE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.FloatArrayMessage)
-  })
-_sym_db.RegisterMessage(FloatArrayMessage)
-
-DoubleArrayMessage = _reflection.GeneratedProtocolMessageType('DoubleArrayMessage', (_message.Message,), {
-  'DESCRIPTOR' : _DOUBLEARRAYMESSAGE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.DoubleArrayMessage)
-  })
-_sym_db.RegisterMessage(DoubleArrayMessage)
-
-GetValueResponse = _reflection.GeneratedProtocolMessageType('GetValueResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVALUERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetValueResponse)
-  })
-_sym_db.RegisterMessage(GetValueResponse)
-
-GetValueAtIndicesRequest = _reflection.GeneratedProtocolMessageType('GetValueAtIndicesRequest', (_message.Message,), {
-  'DESCRIPTOR' : _GETVALUEATINDICESREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetValueAtIndicesRequest)
-  })
-_sym_db.RegisterMessage(GetValueAtIndicesRequest)
-
-GetValueAtIndicesResponse = _reflection.GeneratedProtocolMessageType('GetValueAtIndicesResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETVALUEATINDICESRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetValueAtIndicesResponse)
-  })
-_sym_db.RegisterMessage(GetValueAtIndicesResponse)
-
-SetValueRequest = _reflection.GeneratedProtocolMessageType('SetValueRequest', (_message.Message,), {
-  'DESCRIPTOR' : _SETVALUEREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.SetValueRequest)
-  })
-_sym_db.RegisterMessage(SetValueRequest)
-
-SetValuePtrRequest = _reflection.GeneratedProtocolMessageType('SetValuePtrRequest', (_message.Message,), {
-  'DESCRIPTOR' : _SETVALUEPTRREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.SetValuePtrRequest)
-  })
-_sym_db.RegisterMessage(SetValuePtrRequest)
-
-SetValueAtIndicesRequest = _reflection.GeneratedProtocolMessageType('SetValueAtIndicesRequest', (_message.Message,), {
-  'DESCRIPTOR' : _SETVALUEATINDICESREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.SetValueAtIndicesRequest)
-  })
-_sym_db.RegisterMessage(SetValueAtIndicesRequest)
-
-GridRequest = _reflection.GeneratedProtocolMessageType('GridRequest', (_message.Message,), {
-  'DESCRIPTOR' : _GRIDREQUEST,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GridRequest)
-  })
-_sym_db.RegisterMessage(GridRequest)
-
-GetGridSizeResponse = _reflection.GeneratedProtocolMessageType('GetGridSizeResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDSIZERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridSizeResponse)
-  })
-_sym_db.RegisterMessage(GetGridSizeResponse)
-
-GetGridRankResponse = _reflection.GeneratedProtocolMessageType('GetGridRankResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDRANKRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridRankResponse)
-  })
-_sym_db.RegisterMessage(GetGridRankResponse)
-
-GetGridTypeResponse = _reflection.GeneratedProtocolMessageType('GetGridTypeResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDTYPERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridTypeResponse)
-  })
-_sym_db.RegisterMessage(GetGridTypeResponse)
-
-GetGridShapeResponse = _reflection.GeneratedProtocolMessageType('GetGridShapeResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDSHAPERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridShapeResponse)
-  })
-_sym_db.RegisterMessage(GetGridShapeResponse)
-
-GetGridSpacingResponse = _reflection.GeneratedProtocolMessageType('GetGridSpacingResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDSPACINGRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridSpacingResponse)
-  })
-_sym_db.RegisterMessage(GetGridSpacingResponse)
-
-GetGridOriginResponse = _reflection.GeneratedProtocolMessageType('GetGridOriginResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDORIGINRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridOriginResponse)
-  })
-_sym_db.RegisterMessage(GetGridOriginResponse)
-
-GetGridPointsResponse = _reflection.GeneratedProtocolMessageType('GetGridPointsResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDPOINTSRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridPointsResponse)
-  })
-_sym_db.RegisterMessage(GetGridPointsResponse)
-
-GetCountResponse = _reflection.GeneratedProtocolMessageType('GetCountResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETCOUNTRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetCountResponse)
-  })
-_sym_db.RegisterMessage(GetCountResponse)
-
-GetGridEdgeNodesResponse = _reflection.GeneratedProtocolMessageType('GetGridEdgeNodesResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDEDGENODESRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridEdgeNodesResponse)
-  })
-_sym_db.RegisterMessage(GetGridEdgeNodesResponse)
-
-GetGridFaceEdgesResponse = _reflection.GeneratedProtocolMessageType('GetGridFaceEdgesResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDFACEEDGESRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridFaceEdgesResponse)
-  })
-_sym_db.RegisterMessage(GetGridFaceEdgesResponse)
-
-GetGridFaceNodesResponse = _reflection.GeneratedProtocolMessageType('GetGridFaceNodesResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDFACENODESRESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridFaceNodesResponse)
-  })
-_sym_db.RegisterMessage(GetGridFaceNodesResponse)
-
-GetGridNodesPerFaceResponse = _reflection.GeneratedProtocolMessageType('GetGridNodesPerFaceResponse', (_message.Message,), {
-  'DESCRIPTOR' : _GETGRIDNODESPERFACERESPONSE,
-  '__module__' : 'grpc4bmi.bmi_pb2'
-  # @@protoc_insertion_point(class_scope:bmi.GetGridNodesPerFaceResponse)
-  })
-_sym_db.RegisterMessage(GetGridNodesPerFaceResponse)
-
-
-_INTARRAYMESSAGE.fields_by_name['values']._options = None
-_FLOATARRAYMESSAGE.fields_by_name['values']._options = None
-_DOUBLEARRAYMESSAGE.fields_by_name['values']._options = None
-_GETVALUEATINDICESREQUEST.fields_by_name['indices']._options = None
-_SETVALUEATINDICESREQUEST.fields_by_name['indices']._options = None
-_GETGRIDSHAPERESPONSE.fields_by_name['shape']._options = None
-_GETGRIDSPACINGRESPONSE.fields_by_name['spacing']._options = None
-_GETGRIDORIGINRESPONSE.fields_by_name['origin']._options = None
-_GETGRIDPOINTSRESPONSE.fields_by_name['coordinates']._options = None
-_GETGRIDEDGENODESRESPONSE.fields_by_name['edge_nodes']._options = None
-_GETGRIDFACEEDGESRESPONSE.fields_by_name['face_edges']._options = None
-_GETGRIDFACENODESRESPONSE.fields_by_name['face_nodes']._options = None
-_GETGRIDNODESPERFACERESPONSE.fields_by_name['nodes_per_face']._options = None
-
-_BMISERVICE = _descriptor.ServiceDescriptor(
-  name='BmiService',
-  full_name='bmi.BmiService',
-  file=DESCRIPTOR,
-  index=0,
-  serialized_options=None,
-  serialized_start=2189,
-  serialized_end=4631,
-  methods=[
-  _descriptor.MethodDescriptor(
-    name='initialize',
-    full_name='bmi.BmiService.initialize',
-    index=0,
-    containing_service=None,
-    input_type=_INITIALIZEREQUEST,
-    output_type=_EMPTY,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='update',
-    full_name='bmi.BmiService.update',
-    index=1,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_EMPTY,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='updateUntil',
-    full_name='bmi.BmiService.updateUntil',
-    index=2,
-    containing_service=None,
-    input_type=_GETTIMERESPONSE,
-    output_type=_EMPTY,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='finalize',
-    full_name='bmi.BmiService.finalize',
-    index=3,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_EMPTY,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getComponentName',
-    full_name='bmi.BmiService.getComponentName',
-    index=4,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETCOMPONENTNAMERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getInputItemCount',
-    full_name='bmi.BmiService.getInputItemCount',
-    index=5,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETCOUNTRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getOutputItemCount',
-    full_name='bmi.BmiService.getOutputItemCount',
-    index=6,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETCOUNTRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getInputVarNames',
-    full_name='bmi.BmiService.getInputVarNames',
-    index=7,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETVARNAMESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getOutputVarNames',
-    full_name='bmi.BmiService.getOutputVarNames',
-    index=8,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETVARNAMESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getTimeUnits',
-    full_name='bmi.BmiService.getTimeUnits',
-    index=9,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETTIMEUNITSRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getTimeStep',
-    full_name='bmi.BmiService.getTimeStep',
-    index=10,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETTIMESTEPRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getCurrentTime',
-    full_name='bmi.BmiService.getCurrentTime',
-    index=11,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETTIMERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getStartTime',
-    full_name='bmi.BmiService.getStartTime',
-    index=12,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETTIMERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getEndTime',
-    full_name='bmi.BmiService.getEndTime',
-    index=13,
-    containing_service=None,
-    input_type=_EMPTY,
-    output_type=_GETTIMERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getVarGrid',
-    full_name='bmi.BmiService.getVarGrid',
-    index=14,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVARGRIDRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getVarType',
-    full_name='bmi.BmiService.getVarType',
-    index=15,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVARTYPERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getVarItemSize',
-    full_name='bmi.BmiService.getVarItemSize',
-    index=16,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVARITEMSIZERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getVarUnits',
-    full_name='bmi.BmiService.getVarUnits',
-    index=17,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVARUNITSRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getVarNBytes',
-    full_name='bmi.BmiService.getVarNBytes',
-    index=18,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVARNBYTESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getVarLocation',
-    full_name='bmi.BmiService.getVarLocation',
-    index=19,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVARLOCATIONRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getValue',
-    full_name='bmi.BmiService.getValue',
-    index=20,
-    containing_service=None,
-    input_type=_GETVARREQUEST,
-    output_type=_GETVALUERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getValueAtIndices',
-    full_name='bmi.BmiService.getValueAtIndices',
-    index=21,
-    containing_service=None,
-    input_type=_GETVALUEATINDICESREQUEST,
-    output_type=_GETVALUEATINDICESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='setValue',
-    full_name='bmi.BmiService.setValue',
-    index=22,
-    containing_service=None,
-    input_type=_SETVALUEREQUEST,
-    output_type=_EMPTY,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='setValueAtIndices',
-    full_name='bmi.BmiService.setValueAtIndices',
-    index=23,
-    containing_service=None,
-    input_type=_SETVALUEATINDICESREQUEST,
-    output_type=_EMPTY,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridSize',
-    full_name='bmi.BmiService.getGridSize',
-    index=24,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDSIZERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridType',
-    full_name='bmi.BmiService.getGridType',
-    index=25,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDTYPERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridRank',
-    full_name='bmi.BmiService.getGridRank',
-    index=26,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDRANKRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridShape',
-    full_name='bmi.BmiService.getGridShape',
-    index=27,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDSHAPERESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridSpacing',
-    full_name='bmi.BmiService.getGridSpacing',
-    index=28,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDSPACINGRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridOrigin',
-    full_name='bmi.BmiService.getGridOrigin',
-    index=29,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDORIGINRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridX',
-    full_name='bmi.BmiService.getGridX',
-    index=30,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDPOINTSRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridY',
-    full_name='bmi.BmiService.getGridY',
-    index=31,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDPOINTSRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridZ',
-    full_name='bmi.BmiService.getGridZ',
-    index=32,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDPOINTSRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridNodeCount',
-    full_name='bmi.BmiService.getGridNodeCount',
-    index=33,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETCOUNTRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridEdgeCount',
-    full_name='bmi.BmiService.getGridEdgeCount',
-    index=34,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETCOUNTRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridFaceCount',
-    full_name='bmi.BmiService.getGridFaceCount',
-    index=35,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETCOUNTRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridEdgeNodes',
-    full_name='bmi.BmiService.getGridEdgeNodes',
-    index=36,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDEDGENODESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridFaceNodes',
-    full_name='bmi.BmiService.getGridFaceNodes',
-    index=37,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDFACENODESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridFaceEdges',
-    full_name='bmi.BmiService.getGridFaceEdges',
-    index=38,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDFACEEDGESRESPONSE,
-    serialized_options=None,
-  ),
-  _descriptor.MethodDescriptor(
-    name='getGridNodesPerFace',
-    full_name='bmi.BmiService.getGridNodesPerFace',
-    index=39,
-    containing_service=None,
-    input_type=_GRIDREQUEST,
-    output_type=_GETGRIDNODESPERFACERESPONSE,
-    serialized_options=None,
-  ),
-])
-_sym_db.RegisterServiceDescriptor(_BMISERVICE)
-
-DESCRIPTOR.services_by_name['BmiService'] = _BMISERVICE
-
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12grpc4bmi/bmi.proto\x12\x03\x62mi\"\x07\n\x05\x45mpty\"(\n\x11InitializeRequest\x12\x13\n\x0b\x63onfig_file\x18\x01 \x01(\t\"(\n\x18GetComponentNameResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\"$\n\x13GetVarNamesResponse\x12\r\n\x05names\x18\x01 \x03(\t\"%\n\x14GetTimeUnitsResponse\x12\r\n\x05units\x18\x01 \x01(\t\"\'\n\x13GetTimeStepResponse\x12\x10\n\x08interval\x18\x01 \x01(\x01\"\x1f\n\x0fGetTimeResponse\x12\x0c\n\x04time\x18\x01 \x01(\x01\"\x1d\n\rGetVarRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12GetVarGridResponse\x12\x0f\n\x07grid_id\x18\x01 \x01(\x05\"\"\n\x12GetVarTypeResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\"&\n\x16GetVarItemSizeResponse\x12\x0c\n\x04size\x18\x01 \x01(\x03\"$\n\x13GetVarUnitsResponse\x12\r\n\x05units\x18\x01 \x01(\t\"&\n\x14GetVarNBytesResponse\x12\x0e\n\x06nbytes\x18\x01 \x01(\x03\"z\n\x16GetVarLocationResponse\x12\x36\n\x08location\x18\x01 \x01(\x0e\x32$.bmi.GetVarLocationResponse.Location\"(\n\x08Location\x12\x08\n\x04NODE\x10\x00\x12\x08\n\x04\x45\x44GE\x10\x01\x12\x08\n\x04\x46\x41\x43\x45\x10\x02\"%\n\x0fIntArrayMessage\x12\x12\n\x06values\x18\x01 \x03(\x03\x42\x02\x10\x01\"\'\n\x11\x46loatArrayMessage\x12\x12\n\x06values\x18\x01 \x03(\x02\x42\x02\x10\x01\"(\n\x12\x44oubleArrayMessage\x12\x12\n\x06values\x18\x01 \x03(\x01\x42\x02\x10\x01\"\xaa\x01\n\x10GetValueResponse\x12*\n\nvalues_int\x18\x01 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x02 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x03 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"=\n\x18GetValueAtIndicesRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x07indices\x18\x02 \x03(\x03\x42\x02\x10\x01\"\xb3\x01\n\x19GetValueAtIndicesResponse\x12*\n\nvalues_int\x18\x01 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x02 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x03 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"\xb7\x01\n\x0fSetValueRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12*\n\nvalues_int\x18\x02 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x03 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x04 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"/\n\x12SetValuePtrRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03ref\x18\x02 \x01(\x03\"\xd5\x01\n\x18SetValueAtIndicesRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x07indices\x18\x02 \x03(\x03\x42\x02\x10\x01\x12*\n\nvalues_int\x18\x03 \x01(\x0b\x32\x14.bmi.IntArrayMessageH\x00\x12.\n\x0cvalues_float\x18\x04 \x01(\x0b\x32\x16.bmi.FloatArrayMessageH\x00\x12\x30\n\rvalues_double\x18\x05 \x01(\x0b\x32\x17.bmi.DoubleArrayMessageH\x00\x42\x08\n\x06values\"\x1e\n\x0bGridRequest\x12\x0f\n\x07grid_id\x18\x01 \x01(\x03\"#\n\x13GetGridSizeResponse\x12\x0c\n\x04size\x18\x01 \x01(\x03\"#\n\x13GetGridRankResponse\x12\x0c\n\x04rank\x18\x01 \x01(\x03\"#\n\x13GetGridTypeResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\")\n\x14GetGridShapeResponse\x12\x11\n\x05shape\x18\x01 \x03(\x03\x42\x02\x10\x01\"-\n\x16GetGridSpacingResponse\x12\x13\n\x07spacing\x18\x01 \x03(\x01\x42\x02\x10\x01\"+\n\x15GetGridOriginResponse\x12\x12\n\x06origin\x18\x01 \x03(\x01\x42\x02\x10\x01\"0\n\x15GetGridPointsResponse\x12\x17\n\x0b\x63oordinates\x18\x01 \x03(\x01\x42\x02\x10\x01\"!\n\x10GetCountResponse\x12\r\n\x05\x63ount\x18\x01 \x01(\x03\"2\n\x18GetGridEdgeNodesResponse\x12\x16\n\nedge_nodes\x18\x01 \x03(\x03\x42\x02\x10\x01\"2\n\x18GetGridFaceEdgesResponse\x12\x16\n\nface_edges\x18\x01 \x03(\x03\x42\x02\x10\x01\"2\n\x18GetGridFaceNodesResponse\x12\x16\n\nface_nodes\x18\x01 \x03(\x03\x42\x02\x10\x01\"9\n\x1bGetGridNodesPerFaceResponse\x12\x1a\n\x0enodes_per_face\x18\x01 \x03(\x03\x42\x02\x10\x01\x32\x8a\x13\n\nBmiService\x12\x32\n\ninitialize\x12\x16.bmi.InitializeRequest\x1a\n.bmi.Empty\"\x00\x12\"\n\x06update\x12\n.bmi.Empty\x1a\n.bmi.Empty\"\x00\x12\x31\n\x0bupdateUntil\x12\x14.bmi.GetTimeResponse\x1a\n.bmi.Empty\"\x00\x12$\n\x08\x66inalize\x12\n.bmi.Empty\x1a\n.bmi.Empty\"\x00\x12?\n\x10getComponentName\x12\n.bmi.Empty\x1a\x1d.bmi.GetComponentNameResponse\"\x00\x12\x38\n\x11getInputItemCount\x12\n.bmi.Empty\x1a\x15.bmi.GetCountResponse\"\x00\x12\x39\n\x12getOutputItemCount\x12\n.bmi.Empty\x1a\x15.bmi.GetCountResponse\"\x00\x12:\n\x10getInputVarNames\x12\n.bmi.Empty\x1a\x18.bmi.GetVarNamesResponse\"\x00\x12;\n\x11getOutputVarNames\x12\n.bmi.Empty\x1a\x18.bmi.GetVarNamesResponse\"\x00\x12\x37\n\x0cgetTimeUnits\x12\n.bmi.Empty\x1a\x19.bmi.GetTimeUnitsResponse\"\x00\x12\x35\n\x0bgetTimeStep\x12\n.bmi.Empty\x1a\x18.bmi.GetTimeStepResponse\"\x00\x12\x34\n\x0egetCurrentTime\x12\n.bmi.Empty\x1a\x14.bmi.GetTimeResponse\"\x00\x12\x32\n\x0cgetStartTime\x12\n.bmi.Empty\x1a\x14.bmi.GetTimeResponse\"\x00\x12\x30\n\ngetEndTime\x12\n.bmi.Empty\x1a\x14.bmi.GetTimeResponse\"\x00\x12;\n\ngetVarGrid\x12\x12.bmi.GetVarRequest\x1a\x17.bmi.GetVarGridResponse\"\x00\x12;\n\ngetVarType\x12\x12.bmi.GetVarRequest\x1a\x17.bmi.GetVarTypeResponse\"\x00\x12\x43\n\x0egetVarItemSize\x12\x12.bmi.GetVarRequest\x1a\x1b.bmi.GetVarItemSizeResponse\"\x00\x12=\n\x0bgetVarUnits\x12\x12.bmi.GetVarRequest\x1a\x18.bmi.GetVarUnitsResponse\"\x00\x12?\n\x0cgetVarNBytes\x12\x12.bmi.GetVarRequest\x1a\x19.bmi.GetVarNBytesResponse\"\x00\x12\x43\n\x0egetVarLocation\x12\x12.bmi.GetVarRequest\x1a\x1b.bmi.GetVarLocationResponse\"\x00\x12\x37\n\x08getValue\x12\x12.bmi.GetVarRequest\x1a\x15.bmi.GetValueResponse\"\x00\x12T\n\x11getValueAtIndices\x12\x1d.bmi.GetValueAtIndicesRequest\x1a\x1e.bmi.GetValueAtIndicesResponse\"\x00\x12.\n\x08setValue\x12\x14.bmi.SetValueRequest\x1a\n.bmi.Empty\"\x00\x12@\n\x11setValueAtIndices\x12\x1d.bmi.SetValueAtIndicesRequest\x1a\n.bmi.Empty\"\x00\x12;\n\x0bgetGridSize\x12\x10.bmi.GridRequest\x1a\x18.bmi.GetGridSizeResponse\"\x00\x12;\n\x0bgetGridType\x12\x10.bmi.GridRequest\x1a\x18.bmi.GetGridTypeResponse\"\x00\x12;\n\x0bgetGridRank\x12\x10.bmi.GridRequest\x1a\x18.bmi.GetGridRankResponse\"\x00\x12=\n\x0cgetGridShape\x12\x10.bmi.GridRequest\x1a\x19.bmi.GetGridShapeResponse\"\x00\x12\x41\n\x0egetGridSpacing\x12\x10.bmi.GridRequest\x1a\x1b.bmi.GetGridSpacingResponse\"\x00\x12?\n\rgetGridOrigin\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridOriginResponse\"\x00\x12:\n\x08getGridX\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridPointsResponse\"\x00\x12:\n\x08getGridY\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridPointsResponse\"\x00\x12:\n\x08getGridZ\x12\x10.bmi.GridRequest\x1a\x1a.bmi.GetGridPointsResponse\"\x00\x12=\n\x10getGridNodeCount\x12\x10.bmi.GridRequest\x1a\x15.bmi.GetCountResponse\"\x00\x12=\n\x10getGridEdgeCount\x12\x10.bmi.GridRequest\x1a\x15.bmi.GetCountResponse\"\x00\x12=\n\x10getGridFaceCount\x12\x10.bmi.GridRequest\x1a\x15.bmi.GetCountResponse\"\x00\x12\x45\n\x10getGridEdgeNodes\x12\x10.bmi.GridRequest\x1a\x1d.bmi.GetGridEdgeNodesResponse\"\x00\x12\x45\n\x10getGridFaceNodes\x12\x10.bmi.GridRequest\x1a\x1d.bmi.GetGridFaceNodesResponse\"\x00\x12\x45\n\x10getGridFaceEdges\x12\x10.bmi.GridRequest\x1a\x1d.bmi.GetGridFaceEdgesResponse\"\x00\x12K\n\x13getGridNodesPerFace\x12\x10.bmi.GridRequest\x1a .bmi.GetGridNodesPerFaceResponse\"\x00\x62\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'grpc4bmi.bmi_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+  DESCRIPTOR._options = None
+  _INTARRAYMESSAGE.fields_by_name['values']._options = None
+  _INTARRAYMESSAGE.fields_by_name['values']._serialized_options = b'\020\001'
+  _FLOATARRAYMESSAGE.fields_by_name['values']._options = None
+  _FLOATARRAYMESSAGE.fields_by_name['values']._serialized_options = b'\020\001'
+  _DOUBLEARRAYMESSAGE.fields_by_name['values']._options = None
+  _DOUBLEARRAYMESSAGE.fields_by_name['values']._serialized_options = b'\020\001'
+  _GETVALUEATINDICESREQUEST.fields_by_name['indices']._options = None
+  _GETVALUEATINDICESREQUEST.fields_by_name['indices']._serialized_options = b'\020\001'
+  _SETVALUEATINDICESREQUEST.fields_by_name['indices']._options = None
+  _SETVALUEATINDICESREQUEST.fields_by_name['indices']._serialized_options = b'\020\001'
+  _GETGRIDSHAPERESPONSE.fields_by_name['shape']._options = None
+  _GETGRIDSHAPERESPONSE.fields_by_name['shape']._serialized_options = b'\020\001'
+  _GETGRIDSPACINGRESPONSE.fields_by_name['spacing']._options = None
+  _GETGRIDSPACINGRESPONSE.fields_by_name['spacing']._serialized_options = b'\020\001'
+  _GETGRIDORIGINRESPONSE.fields_by_name['origin']._options = None
+  _GETGRIDORIGINRESPONSE.fields_by_name['origin']._serialized_options = b'\020\001'
+  _GETGRIDPOINTSRESPONSE.fields_by_name['coordinates']._options = None
+  _GETGRIDPOINTSRESPONSE.fields_by_name['coordinates']._serialized_options = b'\020\001'
+  _GETGRIDEDGENODESRESPONSE.fields_by_name['edge_nodes']._options = None
+  _GETGRIDEDGENODESRESPONSE.fields_by_name['edge_nodes']._serialized_options = b'\020\001'
+  _GETGRIDFACEEDGESRESPONSE.fields_by_name['face_edges']._options = None
+  _GETGRIDFACEEDGESRESPONSE.fields_by_name['face_edges']._serialized_options = b'\020\001'
+  _GETGRIDFACENODESRESPONSE.fields_by_name['face_nodes']._options = None
+  _GETGRIDFACENODESRESPONSE.fields_by_name['face_nodes']._serialized_options = b'\020\001'
+  _GETGRIDNODESPERFACERESPONSE.fields_by_name['nodes_per_face']._options = None
+  _GETGRIDNODESPERFACERESPONSE.fields_by_name['nodes_per_face']._serialized_options = b'\020\001'
+  _EMPTY._serialized_start=27
+  _EMPTY._serialized_end=34
+  _INITIALIZEREQUEST._serialized_start=36
+  _INITIALIZEREQUEST._serialized_end=76
+  _GETCOMPONENTNAMERESPONSE._serialized_start=78
+  _GETCOMPONENTNAMERESPONSE._serialized_end=118
+  _GETVARNAMESRESPONSE._serialized_start=120
+  _GETVARNAMESRESPONSE._serialized_end=156
+  _GETTIMEUNITSRESPONSE._serialized_start=158
+  _GETTIMEUNITSRESPONSE._serialized_end=195
+  _GETTIMESTEPRESPONSE._serialized_start=197
+  _GETTIMESTEPRESPONSE._serialized_end=236
+  _GETTIMERESPONSE._serialized_start=238
+  _GETTIMERESPONSE._serialized_end=269
+  _GETVARREQUEST._serialized_start=271
+  _GETVARREQUEST._serialized_end=300
+  _GETVARGRIDRESPONSE._serialized_start=302
+  _GETVARGRIDRESPONSE._serialized_end=339
+  _GETVARTYPERESPONSE._serialized_start=341
+  _GETVARTYPERESPONSE._serialized_end=375
+  _GETVARITEMSIZERESPONSE._serialized_start=377
+  _GETVARITEMSIZERESPONSE._serialized_end=415
+  _GETVARUNITSRESPONSE._serialized_start=417
+  _GETVARUNITSRESPONSE._serialized_end=453
+  _GETVARNBYTESRESPONSE._serialized_start=455
+  _GETVARNBYTESRESPONSE._serialized_end=493
+  _GETVARLOCATIONRESPONSE._serialized_start=495
+  _GETVARLOCATIONRESPONSE._serialized_end=617
+  _GETVARLOCATIONRESPONSE_LOCATION._serialized_start=577
+  _GETVARLOCATIONRESPONSE_LOCATION._serialized_end=617
+  _INTARRAYMESSAGE._serialized_start=619
+  _INTARRAYMESSAGE._serialized_end=656
+  _FLOATARRAYMESSAGE._serialized_start=658
+  _FLOATARRAYMESSAGE._serialized_end=697
+  _DOUBLEARRAYMESSAGE._serialized_start=699
+  _DOUBLEARRAYMESSAGE._serialized_end=739
+  _GETVALUERESPONSE._serialized_start=742
+  _GETVALUERESPONSE._serialized_end=912
+  _GETVALUEATINDICESREQUEST._serialized_start=914
+  _GETVALUEATINDICESREQUEST._serialized_end=975
+  _GETVALUEATINDICESRESPONSE._serialized_start=978
+  _GETVALUEATINDICESRESPONSE._serialized_end=1157
+  _SETVALUEREQUEST._serialized_start=1160
+  _SETVALUEREQUEST._serialized_end=1343
+  _SETVALUEPTRREQUEST._serialized_start=1345
+  _SETVALUEPTRREQUEST._serialized_end=1392
+  _SETVALUEATINDICESREQUEST._serialized_start=1395
+  _SETVALUEATINDICESREQUEST._serialized_end=1608
+  _GRIDREQUEST._serialized_start=1610
+  _GRIDREQUEST._serialized_end=1640
+  _GETGRIDSIZERESPONSE._serialized_start=1642
+  _GETGRIDSIZERESPONSE._serialized_end=1677
+  _GETGRIDRANKRESPONSE._serialized_start=1679
+  _GETGRIDRANKRESPONSE._serialized_end=1714
+  _GETGRIDTYPERESPONSE._serialized_start=1716
+  _GETGRIDTYPERESPONSE._serialized_end=1751
+  _GETGRIDSHAPERESPONSE._serialized_start=1753
+  _GETGRIDSHAPERESPONSE._serialized_end=1794
+  _GETGRIDSPACINGRESPONSE._serialized_start=1796
+  _GETGRIDSPACINGRESPONSE._serialized_end=1841
+  _GETGRIDORIGINRESPONSE._serialized_start=1843
+  _GETGRIDORIGINRESPONSE._serialized_end=1886
+  _GETGRIDPOINTSRESPONSE._serialized_start=1888
+  _GETGRIDPOINTSRESPONSE._serialized_end=1936
+  _GETCOUNTRESPONSE._serialized_start=1938
+  _GETCOUNTRESPONSE._serialized_end=1971
+  _GETGRIDEDGENODESRESPONSE._serialized_start=1973
+  _GETGRIDEDGENODESRESPONSE._serialized_end=2023
+  _GETGRIDFACEEDGESRESPONSE._serialized_start=2025
+  _GETGRIDFACEEDGESRESPONSE._serialized_end=2075
+  _GETGRIDFACENODESRESPONSE._serialized_start=2077
+  _GETGRIDFACENODESRESPONSE._serialized_end=2127
+  _GETGRIDNODESPERFACERESPONSE._serialized_start=2129
+  _GETGRIDNODESPERFACERESPONSE._serialized_end=2186
+  _BMISERVICE._serialized_start=2189
+  _BMISERVICE._serialized_end=4631
 # @@protoc_insertion_point(module_scope)
diff --git a/grpc4bmi/bmi_pb2_grpc.py b/grpc4bmi/bmi_pb2_grpc.py
index 9db5fc7..d6e2ed4 100644
--- a/grpc4bmi/bmi_pb2_grpc.py
+++ b/grpc4bmi/bmi_pb2_grpc.py
@@ -1,709 +1,1353 @@
 # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
 import grpc
 
 from grpc4bmi import bmi_pb2 as grpc4bmi_dot_bmi__pb2
 
 
 class BmiServiceStub(object):
-  # missing associated documentation comment in .proto file
-  pass
-
-  def __init__(self, channel):
-    """Constructor.
-
-    Args:
-      channel: A grpc.Channel.
-    """
-    self.initialize = channel.unary_unary(
-        '/bmi.BmiService/initialize',
-        request_serializer=grpc4bmi_dot_bmi__pb2.InitializeRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-        )
-    self.update = channel.unary_unary(
-        '/bmi.BmiService/update',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-        )
-    self.updateUntil = channel.unary_unary(
-        '/bmi.BmiService/updateUntil',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-        )
-    self.finalize = channel.unary_unary(
-        '/bmi.BmiService/finalize',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-        )
-    self.getComponentName = channel.unary_unary(
-        '/bmi.BmiService/getComponentName',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetComponentNameResponse.FromString,
-        )
-    self.getInputItemCount = channel.unary_unary(
-        '/bmi.BmiService/getInputItemCount',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
-        )
-    self.getOutputItemCount = channel.unary_unary(
-        '/bmi.BmiService/getOutputItemCount',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
-        )
-    self.getInputVarNames = channel.unary_unary(
-        '/bmi.BmiService/getInputVarNames',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.FromString,
-        )
-    self.getOutputVarNames = channel.unary_unary(
-        '/bmi.BmiService/getOutputVarNames',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.FromString,
-        )
-    self.getTimeUnits = channel.unary_unary(
-        '/bmi.BmiService/getTimeUnits',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeUnitsResponse.FromString,
-        )
-    self.getTimeStep = channel.unary_unary(
-        '/bmi.BmiService/getTimeStep',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeStepResponse.FromString,
-        )
-    self.getCurrentTime = channel.unary_unary(
-        '/bmi.BmiService/getCurrentTime',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
-        )
-    self.getStartTime = channel.unary_unary(
-        '/bmi.BmiService/getStartTime',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
-        )
-    self.getEndTime = channel.unary_unary(
-        '/bmi.BmiService/getEndTime',
-        request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
-        )
-    self.getVarGrid = channel.unary_unary(
-        '/bmi.BmiService/getVarGrid',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarGridResponse.FromString,
-        )
-    self.getVarType = channel.unary_unary(
-        '/bmi.BmiService/getVarType',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarTypeResponse.FromString,
-        )
-    self.getVarItemSize = channel.unary_unary(
-        '/bmi.BmiService/getVarItemSize',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarItemSizeResponse.FromString,
-        )
-    self.getVarUnits = channel.unary_unary(
-        '/bmi.BmiService/getVarUnits',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarUnitsResponse.FromString,
-        )
-    self.getVarNBytes = channel.unary_unary(
-        '/bmi.BmiService/getVarNBytes',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarNBytesResponse.FromString,
-        )
-    self.getVarLocation = channel.unary_unary(
-        '/bmi.BmiService/getVarLocation',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarLocationResponse.FromString,
-        )
-    self.getValue = channel.unary_unary(
-        '/bmi.BmiService/getValue',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetValueResponse.FromString,
-        )
-    self.getValueAtIndices = channel.unary_unary(
-        '/bmi.BmiService/getValueAtIndices',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesResponse.FromString,
-        )
-    self.setValue = channel.unary_unary(
-        '/bmi.BmiService/setValue',
-        request_serializer=grpc4bmi_dot_bmi__pb2.SetValueRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-        )
-    self.setValueAtIndices = channel.unary_unary(
-        '/bmi.BmiService/setValueAtIndices',
-        request_serializer=grpc4bmi_dot_bmi__pb2.SetValueAtIndicesRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-        )
-    self.getGridSize = channel.unary_unary(
-        '/bmi.BmiService/getGridSize',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridSizeResponse.FromString,
-        )
-    self.getGridType = channel.unary_unary(
-        '/bmi.BmiService/getGridType',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridTypeResponse.FromString,
-        )
-    self.getGridRank = channel.unary_unary(
-        '/bmi.BmiService/getGridRank',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridRankResponse.FromString,
-        )
-    self.getGridShape = channel.unary_unary(
-        '/bmi.BmiService/getGridShape',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridShapeResponse.FromString,
-        )
-    self.getGridSpacing = channel.unary_unary(
-        '/bmi.BmiService/getGridSpacing',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridSpacingResponse.FromString,
-        )
-    self.getGridOrigin = channel.unary_unary(
-        '/bmi.BmiService/getGridOrigin',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridOriginResponse.FromString,
-        )
-    self.getGridX = channel.unary_unary(
-        '/bmi.BmiService/getGridX',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
-        )
-    self.getGridY = channel.unary_unary(
-        '/bmi.BmiService/getGridY',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
-        )
-    self.getGridZ = channel.unary_unary(
-        '/bmi.BmiService/getGridZ',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
-        )
-    self.getGridNodeCount = channel.unary_unary(
-        '/bmi.BmiService/getGridNodeCount',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
-        )
-    self.getGridEdgeCount = channel.unary_unary(
-        '/bmi.BmiService/getGridEdgeCount',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
-        )
-    self.getGridFaceCount = channel.unary_unary(
-        '/bmi.BmiService/getGridFaceCount',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
-        )
-    self.getGridEdgeNodes = channel.unary_unary(
-        '/bmi.BmiService/getGridEdgeNodes',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridEdgeNodesResponse.FromString,
-        )
-    self.getGridFaceNodes = channel.unary_unary(
-        '/bmi.BmiService/getGridFaceNodes',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridFaceNodesResponse.FromString,
-        )
-    self.getGridFaceEdges = channel.unary_unary(
-        '/bmi.BmiService/getGridFaceEdges',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridFaceEdgesResponse.FromString,
-        )
-    self.getGridNodesPerFace = channel.unary_unary(
-        '/bmi.BmiService/getGridNodesPerFace',
-        request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
-        response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridNodesPerFaceResponse.FromString,
-        )
+    """Missing associated documentation comment in .proto file."""
+
+    def __init__(self, channel):
+        """Constructor.
+
+        Args:
+            channel: A grpc.Channel.
+        """
+        self.initialize = channel.unary_unary(
+                '/bmi.BmiService/initialize',
+                request_serializer=grpc4bmi_dot_bmi__pb2.InitializeRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                )
+        self.update = channel.unary_unary(
+                '/bmi.BmiService/update',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                )
+        self.updateUntil = channel.unary_unary(
+                '/bmi.BmiService/updateUntil',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                )
+        self.finalize = channel.unary_unary(
+                '/bmi.BmiService/finalize',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                )
+        self.getComponentName = channel.unary_unary(
+                '/bmi.BmiService/getComponentName',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetComponentNameResponse.FromString,
+                )
+        self.getInputItemCount = channel.unary_unary(
+                '/bmi.BmiService/getInputItemCount',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+                )
+        self.getOutputItemCount = channel.unary_unary(
+                '/bmi.BmiService/getOutputItemCount',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+                )
+        self.getInputVarNames = channel.unary_unary(
+                '/bmi.BmiService/getInputVarNames',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.FromString,
+                )
+        self.getOutputVarNames = channel.unary_unary(
+                '/bmi.BmiService/getOutputVarNames',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.FromString,
+                )
+        self.getTimeUnits = channel.unary_unary(
+                '/bmi.BmiService/getTimeUnits',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeUnitsResponse.FromString,
+                )
+        self.getTimeStep = channel.unary_unary(
+                '/bmi.BmiService/getTimeStep',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeStepResponse.FromString,
+                )
+        self.getCurrentTime = channel.unary_unary(
+                '/bmi.BmiService/getCurrentTime',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+                )
+        self.getStartTime = channel.unary_unary(
+                '/bmi.BmiService/getStartTime',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+                )
+        self.getEndTime = channel.unary_unary(
+                '/bmi.BmiService/getEndTime',
+                request_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+                )
+        self.getVarGrid = channel.unary_unary(
+                '/bmi.BmiService/getVarGrid',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarGridResponse.FromString,
+                )
+        self.getVarType = channel.unary_unary(
+                '/bmi.BmiService/getVarType',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarTypeResponse.FromString,
+                )
+        self.getVarItemSize = channel.unary_unary(
+                '/bmi.BmiService/getVarItemSize',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarItemSizeResponse.FromString,
+                )
+        self.getVarUnits = channel.unary_unary(
+                '/bmi.BmiService/getVarUnits',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarUnitsResponse.FromString,
+                )
+        self.getVarNBytes = channel.unary_unary(
+                '/bmi.BmiService/getVarNBytes',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarNBytesResponse.FromString,
+                )
+        self.getVarLocation = channel.unary_unary(
+                '/bmi.BmiService/getVarLocation',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetVarLocationResponse.FromString,
+                )
+        self.getValue = channel.unary_unary(
+                '/bmi.BmiService/getValue',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetValueResponse.FromString,
+                )
+        self.getValueAtIndices = channel.unary_unary(
+                '/bmi.BmiService/getValueAtIndices',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesResponse.FromString,
+                )
+        self.setValue = channel.unary_unary(
+                '/bmi.BmiService/setValue',
+                request_serializer=grpc4bmi_dot_bmi__pb2.SetValueRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                )
+        self.setValueAtIndices = channel.unary_unary(
+                '/bmi.BmiService/setValueAtIndices',
+                request_serializer=grpc4bmi_dot_bmi__pb2.SetValueAtIndicesRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                )
+        self.getGridSize = channel.unary_unary(
+                '/bmi.BmiService/getGridSize',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridSizeResponse.FromString,
+                )
+        self.getGridType = channel.unary_unary(
+                '/bmi.BmiService/getGridType',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridTypeResponse.FromString,
+                )
+        self.getGridRank = channel.unary_unary(
+                '/bmi.BmiService/getGridRank',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridRankResponse.FromString,
+                )
+        self.getGridShape = channel.unary_unary(
+                '/bmi.BmiService/getGridShape',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridShapeResponse.FromString,
+                )
+        self.getGridSpacing = channel.unary_unary(
+                '/bmi.BmiService/getGridSpacing',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridSpacingResponse.FromString,
+                )
+        self.getGridOrigin = channel.unary_unary(
+                '/bmi.BmiService/getGridOrigin',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridOriginResponse.FromString,
+                )
+        self.getGridX = channel.unary_unary(
+                '/bmi.BmiService/getGridX',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
+                )
+        self.getGridY = channel.unary_unary(
+                '/bmi.BmiService/getGridY',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
+                )
+        self.getGridZ = channel.unary_unary(
+                '/bmi.BmiService/getGridZ',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
+                )
+        self.getGridNodeCount = channel.unary_unary(
+                '/bmi.BmiService/getGridNodeCount',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+                )
+        self.getGridEdgeCount = channel.unary_unary(
+                '/bmi.BmiService/getGridEdgeCount',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+                )
+        self.getGridFaceCount = channel.unary_unary(
+                '/bmi.BmiService/getGridFaceCount',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+                )
+        self.getGridEdgeNodes = channel.unary_unary(
+                '/bmi.BmiService/getGridEdgeNodes',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridEdgeNodesResponse.FromString,
+                )
+        self.getGridFaceNodes = channel.unary_unary(
+                '/bmi.BmiService/getGridFaceNodes',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridFaceNodesResponse.FromString,
+                )
+        self.getGridFaceEdges = channel.unary_unary(
+                '/bmi.BmiService/getGridFaceEdges',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridFaceEdgesResponse.FromString,
+                )
+        self.getGridNodesPerFace = channel.unary_unary(
+                '/bmi.BmiService/getGridNodesPerFace',
+                request_serializer=grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+                response_deserializer=grpc4bmi_dot_bmi__pb2.GetGridNodesPerFaceResponse.FromString,
+                )
 
 
 class BmiServiceServicer(object):
-  # missing associated documentation comment in .proto file
-  pass
-
-  def initialize(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def update(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def updateUntil(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def finalize(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getComponentName(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getInputItemCount(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getOutputItemCount(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getInputVarNames(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getOutputVarNames(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getTimeUnits(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getTimeStep(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getCurrentTime(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getStartTime(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getEndTime(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getVarGrid(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getVarType(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getVarItemSize(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getVarUnits(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getVarNBytes(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getVarLocation(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getValue(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getValueAtIndices(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def setValue(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def setValueAtIndices(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridSize(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridType(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridRank(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridShape(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridSpacing(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridOrigin(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridX(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridY(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridZ(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridNodeCount(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridEdgeCount(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridFaceCount(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridEdgeNodes(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridFaceNodes(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridFaceEdges(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
-
-  def getGridNodesPerFace(self, request, context):
-    # missing associated documentation comment in .proto file
-    pass
-    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-    context.set_details('Method not implemented!')
-    raise NotImplementedError('Method not implemented!')
+    """Missing associated documentation comment in .proto file."""
+
+    def initialize(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def update(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def updateUntil(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def finalize(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getComponentName(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getInputItemCount(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getOutputItemCount(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getInputVarNames(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getOutputVarNames(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getTimeUnits(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getTimeStep(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getCurrentTime(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getStartTime(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getEndTime(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getVarGrid(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getVarType(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getVarItemSize(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getVarUnits(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getVarNBytes(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getVarLocation(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getValue(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getValueAtIndices(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def setValue(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def setValueAtIndices(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridSize(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridType(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridRank(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridShape(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridSpacing(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridOrigin(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridX(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridY(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridZ(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridNodeCount(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridEdgeCount(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridFaceCount(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridEdgeNodes(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridFaceNodes(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridFaceEdges(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def getGridNodesPerFace(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
 
 
 def add_BmiServiceServicer_to_server(servicer, server):
-  rpc_method_handlers = {
-      'initialize': grpc.unary_unary_rpc_method_handler(
-          servicer.initialize,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.InitializeRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-      ),
-      'update': grpc.unary_unary_rpc_method_handler(
-          servicer.update,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-      ),
-      'updateUntil': grpc.unary_unary_rpc_method_handler(
-          servicer.updateUntil,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-      ),
-      'finalize': grpc.unary_unary_rpc_method_handler(
-          servicer.finalize,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-      ),
-      'getComponentName': grpc.unary_unary_rpc_method_handler(
-          servicer.getComponentName,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetComponentNameResponse.SerializeToString,
-      ),
-      'getInputItemCount': grpc.unary_unary_rpc_method_handler(
-          servicer.getInputItemCount,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
-      ),
-      'getOutputItemCount': grpc.unary_unary_rpc_method_handler(
-          servicer.getOutputItemCount,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
-      ),
-      'getInputVarNames': grpc.unary_unary_rpc_method_handler(
-          servicer.getInputVarNames,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.SerializeToString,
-      ),
-      'getOutputVarNames': grpc.unary_unary_rpc_method_handler(
-          servicer.getOutputVarNames,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.SerializeToString,
-      ),
-      'getTimeUnits': grpc.unary_unary_rpc_method_handler(
-          servicer.getTimeUnits,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeUnitsResponse.SerializeToString,
-      ),
-      'getTimeStep': grpc.unary_unary_rpc_method_handler(
-          servicer.getTimeStep,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeStepResponse.SerializeToString,
-      ),
-      'getCurrentTime': grpc.unary_unary_rpc_method_handler(
-          servicer.getCurrentTime,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
-      ),
-      'getStartTime': grpc.unary_unary_rpc_method_handler(
-          servicer.getStartTime,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
-      ),
-      'getEndTime': grpc.unary_unary_rpc_method_handler(
-          servicer.getEndTime,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
-      ),
-      'getVarGrid': grpc.unary_unary_rpc_method_handler(
-          servicer.getVarGrid,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarGridResponse.SerializeToString,
-      ),
-      'getVarType': grpc.unary_unary_rpc_method_handler(
-          servicer.getVarType,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarTypeResponse.SerializeToString,
-      ),
-      'getVarItemSize': grpc.unary_unary_rpc_method_handler(
-          servicer.getVarItemSize,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarItemSizeResponse.SerializeToString,
-      ),
-      'getVarUnits': grpc.unary_unary_rpc_method_handler(
-          servicer.getVarUnits,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarUnitsResponse.SerializeToString,
-      ),
-      'getVarNBytes': grpc.unary_unary_rpc_method_handler(
-          servicer.getVarNBytes,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarNBytesResponse.SerializeToString,
-      ),
-      'getVarLocation': grpc.unary_unary_rpc_method_handler(
-          servicer.getVarLocation,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetVarLocationResponse.SerializeToString,
-      ),
-      'getValue': grpc.unary_unary_rpc_method_handler(
-          servicer.getValue,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetValueResponse.SerializeToString,
-      ),
-      'getValueAtIndices': grpc.unary_unary_rpc_method_handler(
-          servicer.getValueAtIndices,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesResponse.SerializeToString,
-      ),
-      'setValue': grpc.unary_unary_rpc_method_handler(
-          servicer.setValue,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.SetValueRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-      ),
-      'setValueAtIndices': grpc.unary_unary_rpc_method_handler(
-          servicer.setValueAtIndices,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.SetValueAtIndicesRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
-      ),
-      'getGridSize': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridSize,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridSizeResponse.SerializeToString,
-      ),
-      'getGridType': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridType,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridTypeResponse.SerializeToString,
-      ),
-      'getGridRank': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridRank,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridRankResponse.SerializeToString,
-      ),
-      'getGridShape': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridShape,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridShapeResponse.SerializeToString,
-      ),
-      'getGridSpacing': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridSpacing,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridSpacingResponse.SerializeToString,
-      ),
-      'getGridOrigin': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridOrigin,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridOriginResponse.SerializeToString,
-      ),
-      'getGridX': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridX,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.SerializeToString,
-      ),
-      'getGridY': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridY,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.SerializeToString,
-      ),
-      'getGridZ': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridZ,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.SerializeToString,
-      ),
-      'getGridNodeCount': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridNodeCount,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
-      ),
-      'getGridEdgeCount': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridEdgeCount,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
-      ),
-      'getGridFaceCount': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridFaceCount,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
-      ),
-      'getGridEdgeNodes': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridEdgeNodes,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridEdgeNodesResponse.SerializeToString,
-      ),
-      'getGridFaceNodes': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridFaceNodes,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridFaceNodesResponse.SerializeToString,
-      ),
-      'getGridFaceEdges': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridFaceEdges,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridFaceEdgesResponse.SerializeToString,
-      ),
-      'getGridNodesPerFace': grpc.unary_unary_rpc_method_handler(
-          servicer.getGridNodesPerFace,
-          request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
-          response_serializer=grpc4bmi_dot_bmi__pb2.GetGridNodesPerFaceResponse.SerializeToString,
-      ),
-  }
-  generic_handler = grpc.method_handlers_generic_handler(
-      'bmi.BmiService', rpc_method_handlers)
-  server.add_generic_rpc_handlers((generic_handler,))
+    rpc_method_handlers = {
+            'initialize': grpc.unary_unary_rpc_method_handler(
+                    servicer.initialize,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.InitializeRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            ),
+            'update': grpc.unary_unary_rpc_method_handler(
+                    servicer.update,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            ),
+            'updateUntil': grpc.unary_unary_rpc_method_handler(
+                    servicer.updateUntil,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            ),
+            'finalize': grpc.unary_unary_rpc_method_handler(
+                    servicer.finalize,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            ),
+            'getComponentName': grpc.unary_unary_rpc_method_handler(
+                    servicer.getComponentName,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetComponentNameResponse.SerializeToString,
+            ),
+            'getInputItemCount': grpc.unary_unary_rpc_method_handler(
+                    servicer.getInputItemCount,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
+            ),
+            'getOutputItemCount': grpc.unary_unary_rpc_method_handler(
+                    servicer.getOutputItemCount,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
+            ),
+            'getInputVarNames': grpc.unary_unary_rpc_method_handler(
+                    servicer.getInputVarNames,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.SerializeToString,
+            ),
+            'getOutputVarNames': grpc.unary_unary_rpc_method_handler(
+                    servicer.getOutputVarNames,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.SerializeToString,
+            ),
+            'getTimeUnits': grpc.unary_unary_rpc_method_handler(
+                    servicer.getTimeUnits,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeUnitsResponse.SerializeToString,
+            ),
+            'getTimeStep': grpc.unary_unary_rpc_method_handler(
+                    servicer.getTimeStep,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeStepResponse.SerializeToString,
+            ),
+            'getCurrentTime': grpc.unary_unary_rpc_method_handler(
+                    servicer.getCurrentTime,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
+            ),
+            'getStartTime': grpc.unary_unary_rpc_method_handler(
+                    servicer.getStartTime,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
+            ),
+            'getEndTime': grpc.unary_unary_rpc_method_handler(
+                    servicer.getEndTime,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.Empty.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
+            ),
+            'getVarGrid': grpc.unary_unary_rpc_method_handler(
+                    servicer.getVarGrid,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarGridResponse.SerializeToString,
+            ),
+            'getVarType': grpc.unary_unary_rpc_method_handler(
+                    servicer.getVarType,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarTypeResponse.SerializeToString,
+            ),
+            'getVarItemSize': grpc.unary_unary_rpc_method_handler(
+                    servicer.getVarItemSize,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarItemSizeResponse.SerializeToString,
+            ),
+            'getVarUnits': grpc.unary_unary_rpc_method_handler(
+                    servicer.getVarUnits,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarUnitsResponse.SerializeToString,
+            ),
+            'getVarNBytes': grpc.unary_unary_rpc_method_handler(
+                    servicer.getVarNBytes,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarNBytesResponse.SerializeToString,
+            ),
+            'getVarLocation': grpc.unary_unary_rpc_method_handler(
+                    servicer.getVarLocation,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetVarLocationResponse.SerializeToString,
+            ),
+            'getValue': grpc.unary_unary_rpc_method_handler(
+                    servicer.getValue,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetVarRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetValueResponse.SerializeToString,
+            ),
+            'getValueAtIndices': grpc.unary_unary_rpc_method_handler(
+                    servicer.getValueAtIndices,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetValueAtIndicesResponse.SerializeToString,
+            ),
+            'setValue': grpc.unary_unary_rpc_method_handler(
+                    servicer.setValue,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.SetValueRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            ),
+            'setValueAtIndices': grpc.unary_unary_rpc_method_handler(
+                    servicer.setValueAtIndices,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.SetValueAtIndicesRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            ),
+            'getGridSize': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridSize,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridSizeResponse.SerializeToString,
+            ),
+            'getGridType': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridType,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridTypeResponse.SerializeToString,
+            ),
+            'getGridRank': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridRank,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridRankResponse.SerializeToString,
+            ),
+            'getGridShape': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridShape,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridShapeResponse.SerializeToString,
+            ),
+            'getGridSpacing': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridSpacing,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridSpacingResponse.SerializeToString,
+            ),
+            'getGridOrigin': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridOrigin,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridOriginResponse.SerializeToString,
+            ),
+            'getGridX': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridX,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.SerializeToString,
+            ),
+            'getGridY': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridY,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.SerializeToString,
+            ),
+            'getGridZ': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridZ,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.SerializeToString,
+            ),
+            'getGridNodeCount': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridNodeCount,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
+            ),
+            'getGridEdgeCount': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridEdgeCount,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
+            ),
+            'getGridFaceCount': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridFaceCount,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetCountResponse.SerializeToString,
+            ),
+            'getGridEdgeNodes': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridEdgeNodes,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridEdgeNodesResponse.SerializeToString,
+            ),
+            'getGridFaceNodes': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridFaceNodes,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridFaceNodesResponse.SerializeToString,
+            ),
+            'getGridFaceEdges': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridFaceEdges,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridFaceEdgesResponse.SerializeToString,
+            ),
+            'getGridNodesPerFace': grpc.unary_unary_rpc_method_handler(
+                    servicer.getGridNodesPerFace,
+                    request_deserializer=grpc4bmi_dot_bmi__pb2.GridRequest.FromString,
+                    response_serializer=grpc4bmi_dot_bmi__pb2.GetGridNodesPerFaceResponse.SerializeToString,
+            ),
+    }
+    generic_handler = grpc.method_handlers_generic_handler(
+            'bmi.BmiService', rpc_method_handlers)
+    server.add_generic_rpc_handlers((generic_handler,))
+
+
+ # This class is part of an EXPERIMENTAL API.
+class BmiService(object):
+    """Missing associated documentation comment in .proto file."""
+
+    @staticmethod
+    def initialize(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/initialize',
+            grpc4bmi_dot_bmi__pb2.InitializeRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.Empty.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def update(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/update',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.Empty.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def updateUntil(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/updateUntil',
+            grpc4bmi_dot_bmi__pb2.GetTimeResponse.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.Empty.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def finalize(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/finalize',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.Empty.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getComponentName(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getComponentName',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetComponentNameResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getInputItemCount(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getInputItemCount',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getOutputItemCount(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getOutputItemCount',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getInputVarNames(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getInputVarNames',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getOutputVarNames(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getOutputVarNames',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarNamesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getTimeUnits(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getTimeUnits',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetTimeUnitsResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getTimeStep(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getTimeStep',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetTimeStepResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getCurrentTime(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getCurrentTime',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getStartTime(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getStartTime',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getEndTime(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getEndTime',
+            grpc4bmi_dot_bmi__pb2.Empty.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetTimeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getVarGrid(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getVarGrid',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarGridResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getVarType(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getVarType',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarTypeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getVarItemSize(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getVarItemSize',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarItemSizeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getVarUnits(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getVarUnits',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarUnitsResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getVarNBytes(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getVarNBytes',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarNBytesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getVarLocation(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getVarLocation',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetVarLocationResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getValue(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getValue',
+            grpc4bmi_dot_bmi__pb2.GetVarRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetValueResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getValueAtIndices(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getValueAtIndices',
+            grpc4bmi_dot_bmi__pb2.GetValueAtIndicesRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetValueAtIndicesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def setValue(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/setValue',
+            grpc4bmi_dot_bmi__pb2.SetValueRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.Empty.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def setValueAtIndices(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/setValueAtIndices',
+            grpc4bmi_dot_bmi__pb2.SetValueAtIndicesRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.Empty.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridSize(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridSize',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridSizeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridType(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridType',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridTypeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridRank(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridRank',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridRankResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridShape(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridShape',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridShapeResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridSpacing(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridSpacing',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridSpacingResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridOrigin(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridOrigin',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridOriginResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridX(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridX',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridY(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridY',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridZ(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridZ',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridPointsResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridNodeCount(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridNodeCount',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridEdgeCount(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridEdgeCount',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridFaceCount(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridFaceCount',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetCountResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridEdgeNodes(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridEdgeNodes',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridEdgeNodesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridFaceNodes(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridFaceNodes',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridFaceNodesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridFaceEdges(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridFaceEdges',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridFaceEdgesResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def getGridNodesPerFace(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/bmi.BmiService/getGridNodesPerFace',
+            grpc4bmi_dot_bmi__pb2.GridRequest.SerializeToString,
+            grpc4bmi_dot_bmi__pb2.GetGridNodesPerFaceResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
diff --git a/grpc4bmi/bmi_r_model.py b/grpc4bmi/bmi_r_model.py
index d0522e3..c331e89 100644
--- a/grpc4bmi/bmi_r_model.py
+++ b/grpc4bmi/bmi_r_model.py
@@ -34,6 +34,9 @@ def initialize(self, filename):
     def update(self):
         self.model['update']()
 
+    def update_until(self, time):
+        self.model['updateUntil'](time)
+
     def finalize(self):
         self.model['bmi_finalize']()
 
@@ -41,6 +44,12 @@ def finalize(self):
     def get_component_name(self):
         return self.model['getComponentName']()[0]
 
+    def get_input_item_count(self):
+        return self.model['getInputItemCount']()
+
+    def get_output_item_count(self):
+        return self.model['getOutputItemCount']()
+
     def get_input_var_names(self):
         return self.model['getInputVarNames']()
 
@@ -175,6 +184,11 @@ def get_grid_face_nodes(self, grid, face_nodes):
         np.copyto(src=result, dst=face_nodes)
         return face_nodes
 
+    def get_grid_face_edges(self, grid, face_edges):
+        result = self.model['getGridFaceEdges'](grid)
+        np.copyto(src=result, dst=face_edges)
+        return face_edges
+
     def get_grid_nodes_per_face(self, grid, edge_nodes):
         result = self.model['getGridNodesPerFace'](grid)
         np.copyto(src=result, dst=edge_nodes)
diff --git a/grpc4bmi/constants.py b/grpc4bmi/constants.py
new file mode 100644
index 0000000..4440f66
--- /dev/null
+++ b/grpc4bmi/constants.py
@@ -0,0 +1,2 @@
+GRPC_MAX_MESSAGE_LENGTH = 4 * 1024 * 1024
+"""grpc max message size is 4Mb"""
diff --git a/grpc4bmi/exceptions.py b/grpc4bmi/exceptions.py
new file mode 100644
index 0000000..6f6b4d2
--- /dev/null
+++ b/grpc4bmi/exceptions.py
@@ -0,0 +1,24 @@
+
+class DeadContainerException(ChildProcessError):
+    """
+    Exception for when a container has died.
+
+    Args:
+        message (str): Human readable error message
+        exitcode (int): The non-zero exit code of the container
+        logs (str): Logs the container produced
+
+    """
+    def __init__(self, message, exitcode, logs, *args):
+        super().__init__(message, *args)
+        #: Exit code of container
+        self.exitcode = exitcode
+        #: Stdout and stderr of container
+        self.logs = logs
+
+
+class SingularityVersionException(ValueError):
+    pass
+
+class ApptainerVersionException(ValueError):
+    pass
diff --git a/grpc4bmi/py.typed b/grpc4bmi/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/grpc4bmi/reserve.py b/grpc4bmi/reserve.py
index 4ab1919..e8e3c52 100644
--- a/grpc4bmi/reserve.py
+++ b/grpc4bmi/reserve.py
@@ -1,7 +1,6 @@
 """Helpers to reserve numpy arrays for use in some of the Bmi methods as output argument
 """
 import numpy
-
 from bmipy import Bmi
 
 
@@ -43,3 +42,19 @@ def reserve_values_at_indices(model: Bmi, name: str, indices) -> numpy.ndarray:
     """Reserve dest for :func:`bmipy.Bmi.get_value_at_indices` """
     dtype = model.get_var_type(name)
     return numpy.empty(len(indices), dtype=dtype)
+
+def reserve_grid_edge_nodes(model: Bmi, grid_id: int) -> numpy.ndarray:
+    """Reserve edge_nodes for :func:`bmipy.Bmi.get_grid_edge_nodes`"""
+    edge_count = model.get_grid_edge_count(grid_id)
+    return numpy.empty(2 * edge_count, dtype=numpy.int64)
+
+def reserve_grid_nodes_per_face(model: Bmi, grid_id: int) -> numpy.ndarray:
+    """Reserve nodes_per_face for :func:`bmipy.Bmi.get_grid_nodes_per_face`"""
+    face_count = model.get_grid_face_count(grid_id)
+    return numpy.empty(face_count, dtype=numpy.int64)
+
+def reserve_grid_face_(model: Bmi, grid_id: int) -> numpy.ndarray:
+    """Reserve face_edges or face_node in respectivly :func:`bmipy.Bmi.get_grid_face_edges` or :func:`bmipy.Bmi.get_grid_face_nodes`"""
+    nodes_per_face = reserve_grid_nodes_per_face(model, grid_id)
+    model.get_grid_nodes_per_face(grid_id, nodes_per_face)
+    return numpy.empty(nodes_per_face.sum(), dtype=numpy.int64)
diff --git a/grpc4bmi/run_server.py b/grpc4bmi/run_server.py
index c860aca..eb04fcd 100755
--- a/grpc4bmi/run_server.py
+++ b/grpc4bmi/run_server.py
@@ -144,7 +144,7 @@ def build_parser():
         lang_choices.append('R')
     parser.add_argument("--language", default="python", choices=lang_choices,
                         help="Language in which BMI implementation class is written")
-    parser.add_argument("--bmi-version", default="1.0.0", choices=["1.0.0", "0.2"],
+    parser.add_argument("--bmi-version", default="2.0.0", choices=["2.0.0", "0.2"],
                         help="Version of BMI interface implemented by model")
     parser.add_argument("--debug", action="store_true",
                         help="Run server in debug mode. "
diff --git a/grpc4bmi/utils.py b/grpc4bmi/utils.py
deleted file mode 100644
index 5600d9c..0000000
--- a/grpc4bmi/utils.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import os
-import shutil
-from genericpath import exists
-from os.path import abspath, expanduser
-
-
-def stage_config_file(filename, input_dir, input_mount_point, home_mounted=False):
-    """Stage config file inside container
-
-    Args:
-        filename (str): Path to config file
-        input_dir (str): The input directory outside the container
-        input_mount_point (str): The input directory inside the container
-        home_mounted (bool): True if home directory is mounted inside container
-    """
-    fn = filename
-    is_filename_inside_input_dir = input_dir and abspath(filename).startswith(abspath(input_dir))
-    is_filename_inside_home_dir = home_mounted and abspath(filename).startswith(expanduser('~'))
-    filename_exists = exists(filename)
-    if is_filename_inside_input_dir:
-        # Replace input dir outside container by input dir inside container
-        fn = abspath(filename).replace(abspath(input_dir), input_mount_point)
-    elif is_filename_inside_home_dir:
-        # Singularity has home dir mounted, so valid filename should be available inside container
-        # Make absolute because current working dir can be different inside image
-        fn = abspath(filename)
-    elif filename_exists:
-        if input_dir is not None:
-            # Copying filename inside input dir
-            shutil.copy(filename, input_dir)
-            fname = os.path.basename(filename)
-            fn = os.path.join(input_mount_point, fname)
-        else:
-            raise Exception(f'Unable to copy {filename}, without a input_dir')
-    else:
-        # Assume filename exists inside container or model does not need a file to initialize
-        pass
-    return fn
-
-
-# grpc max message size is 4Mb
-# Orginaly set in https://github.com/grpc/grpc/blob/v1.31.x/include/grpc/impl/codegen/grpc_types.h#L464 as GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH define
-GRPC_MAX_MESSAGE_LENGTH = 4 * 1024 * 1024
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..5e59bba
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,85 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "grpc4bmi"
+version = "0.4.0"
+description = "Run your BMI implementation in a separate process and expose it as BMI-python with GRPC"
+readme = "README.md"
+requires-python = ">=3.7"
+license = { text = "Apache License 2.0" }
+classifiers = [
+    "Development Status :: 3 - Alpha",
+    "Intended Audience :: Science/Research",
+    "Programming Language :: Python",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Topic :: Utilities",
+    "Topic :: Scientific/Engineering",
+    "License :: OSI Approved :: Apache Software License"
+]
+dependencies = [
+    "grpcio",
+    "grpcio-reflection",
+    "grpcio-status",
+    "googleapis-common-protos>=1.5.5",
+    "protobuf>=4,<5",
+    "numpy",
+    "docker",
+    "bmipy",
+    "packaging",
+    "typeguard",
+]
+
+[project.optional-dependencies]
+R = ["rpy2"]
+dev = [
+    "build",
+    "pytest",
+    "pytest-cov",
+    "coverage[toml]",
+    "grpcio-tools", # Used to generate Python bindings from proto file
+    "bmi-heat@https://github.com/csdms/bmi-example-python/archive/refs/heads/master.zip",
+    "nbconvert",
+    "ipykernel",
+    "nbformat",
+]
+docs = [
+    "sphinx",
+    "sphinxcontrib-apidoc",
+    "sphinxcontrib-napoleon",
+    "sphinx-argparse",
+    "sphinx_rtd_theme",
+    "numpydoc",
+    "sphinx-copybutton",
+]
+
+[project.scripts]
+run-bmi-server = "grpc4bmi.run_server:main"
+
+[project.urls]
+"Homepage" = "https://github.com/eWaterCycle/grpc4bmi"
+"Bug Tracker" = "https://github.com/eWaterCycle/grpc4bmi/issues"
+"Documentation" = "https://grpc4bmi.readthedocs.io/"
+"Source code" = "https://github.com/eWaterCycle/grpc4bmi"
+
+[tool.setuptools.packages.find]
+where = ["."]
+include = ["grpc4bmi"]
+namespaces = false
+
+[tool.setuptools.package-data]
+grpc4bmi = ["py.typed"]
+
+[tool.coverage.run]
+branch = true
+
+[tool.pytest.ini_options]
+testpaths = [
+    "test",
+]
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 09e9eeb..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-setuptools
-docutils
-PyYAML
-numpydoc
-cfunits
-scipy
-docker
-pytest
-pytest-cov
-futures
-https://github.com/csdms/bmi-example-python/archive/master.zip#egg=bmi-heat
-sphinx
-sphinxcontrib-napoleon
-sphinx-argparse
-sphinx_rtd_theme
-nbconvert
-ipykernel
-nbformat
-grpcio-tools==1.27.2
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 7a2cf50..0000000
--- a/setup.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import os
-from setuptools import setup, find_packages
-
-
-# Utility function to read the README file.
-# Used for the long_description.  It's nice, because now 1) we have a top level
-# README file and 2) it's easier to type in the README file than to put a raw
-# string in below ...
-def read(fname):
-    return open(os.path.join(os.path.dirname(__file__), fname)).read()
-
-
-setup(name="grpc4bmi",
-      version="0.3.2",
-      author="Gijs van den Oord",
-      author_email="g.vandenoord@esciencecenter.nl",
-      description="Run your BMI implementation in a separate process and expose it as BMI-python with GRPC",
-      license="Apache License, Version 2.0",
-      url="https://github.com/eWaterCycle/grpc4bmi",
-      packages=find_packages(),
-      include_package_data=True,
-      long_description=read("README.md"),
-      long_description_content_type='text/markdown',
-      entry_points={"console_scripts": [
-          "run-bmi-server =  grpc4bmi.run_server:main"
-      ]},
-      install_requires=[
-          "grpcio==1.27.2",
-          "grpcio-reflection==1.27.2",
-          "grpcio-status==1.27.2",
-          "googleapis-common-protos>=1.5.5",
-          "protobuf==3.15.0",
-          "numpy",
-          "docker",
-          "bmipy",
-          "semver",
-      ],
-      extras_require={
-          'R': ['rpy2'],
-      },
-      classifiers=["Development Status :: 3 - Alpha",
-                   "Intended Audience :: Science/Research",
-                   "Programming Language :: Python",
-                   "Operating System :: OS Independent",
-                   "Programming Language :: Python :: 3.5",
-                   "Programming Language :: Python :: 3.6",
-                   "Programming Language :: Python :: 3.7",
-                   "Topic :: Utilities",
-                   "Topic :: Scientific/Engineering",
-                   "License :: OSI Approved :: Apache Software License"
-                   ],
-      )
diff --git a/sonar-project.properties b/sonar-project.properties
index 6e4d1e8..393b5f4 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -2,6 +2,8 @@ sonar.projectKey=grpc4bmi
 sonar.host.url=https://sonarcloud.io
 sonar.organization=ewatercycle
 sonar.sources=grpc4bmi/
+# Generated by the protocol buffer compiler
+sonar.exclusions=grpc4bmi/bmi_pb2.py,grpc4bmi/bmi_pb2_grpc.py
 sonar.python.coverage.reportPaths=coverage.xml
 # Disable cpp analysis as it is a paid feature, not available for open source projects on sonarcloud.io
 sonar.c.file.suffixes=-


Test patch:
diff --git a/test/conftest.py b/test/conftest.py
index f693d0a..075bee8 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,7 +1,7 @@
 import pytest
 
 
-def write_config(p, data_fn='/data/input/PEQ_Hupsel.dat'):
+def write_config(p, data_fn):
     p.write_text(f"""data: {data_fn}
 parameters:
   cW: 200
@@ -57,22 +57,24 @@ def write_datafile(p):
 @pytest.fixture
 def walrus_input(tmp_path):
     cfg = tmp_path / 'config.yml'
-    write_config(cfg)
-    write_datafile(tmp_path / 'PEQ_Hupsel.dat')
+    df = tmp_path / 'PEQ_Hupsel.dat'
+    write_config(cfg, df)
+    write_datafile(df)
     return cfg
 
 
 @pytest.fixture()
-def walrus_input_on_extra_volume(tmp_path):
-    # Have config in input dir and forcings data file on extra volume
+def walrus_2input_dirs(tmp_path):
+    # Have config in input dir and forcings data file in forcings dir
+    forcings_dir = tmp_path / 'forcings'
+    forcings_dir.mkdir()
+    df = forcings_dir / 'PEQ_Hupsel.dat'
+    write_datafile(df)
     input_dir = tmp_path / 'input'
     input_dir.mkdir()
     cfg = input_dir / 'config.yml'
-    write_config(cfg, '/forcings/PEQ_Hupsel.dat')
-    extra_dir = tmp_path / 'forcings'
-    extra_dir.mkdir()
-    write_datafile(extra_dir / 'PEQ_Hupsel.dat')
-    extra_volumes = {extra_dir: {'bind': '/forcings', 'mode': 'ro'}}
-    return input_dir, extra_volumes
-
-
+    write_config(cfg, df)
+    return {
+        'input_dirs': (input_dir, forcings_dir),
+        'cfg': str(cfg)
+    }
diff --git a/test/fake.r b/test/fake.r
new file mode 100644
index 0000000..eee5586
--- /dev/null
+++ b/test/fake.r
@@ -0,0 +1,55 @@
+library(R6)
+
+FakeFailingRModel <- R6Class(
+  public = list(
+    # R6 constructor is also called initialize so rename bmi initialize
+    bmi_initialize = function(config_file) stop('Always fails'),
+    update = function() stop('Always fails'),
+    updateUntil = function(until) stop('Always fails'),
+    # R6 destructor is also called finalize so rename bmi finalize
+    bmi_finalize = function() stop('Always fails'),
+    runModel = function() stop('Always fails'),
+
+    getComponentName = function() stop('Always fails'),
+    getInputItemCount = function() stop('Always fails'),
+    getOutputItemCount = function() stop('Always fails'),  
+    getInputVarNames = function() stop('Always fails'),
+    getOutputVarNames = function() stop('Always fails'),
+
+    getTimeUnits = function() stop('Always fails'),
+    getTimeStep = function() stop('Always fails'),
+    getCurrentTime = function() stop('Always fails'),
+    getStartTime = function() stop('Always fails'),
+    getEndTime = function() stop('Always fails'),
+
+    getVarGrid = function(name) stop('Always fails'),
+    getVarType = function(name) stop('Always fails'),
+    getVarItemSize = function(name) stop('Always fails'),
+    getVarUnits = function(name) stop('Always fails'),
+    getVarNBytes = function(name) stop('Always fails'),
+    getVarLocation = function(name) stop('Always fails'),
+
+    getValue = function(name) stop('Always fails'),
+    getValueAtIndices = function(name, indices) stop('Always fails'),
+
+    setValue = function(name, values) stop('Always fails'),
+    setValueAtIndices = function(name, indices, values) stop('Always fails'),
+
+    getGridSize = function(grid_id) stop('Always fails'),
+    getGridType = function(grid_id) stop('Always fails'),
+    getGridRank = function(grid_id) stop('Always fails'),
+    getGridShape = function(grid_id) stop('Always fails'),
+    getGridSpacing = function(grid_id) stop('Always fails'),
+    getGridOrigin = function(grid_id) stop('Always fails'),
+    getGridX = function(grid_id) stop('Always fails'),
+    getGridY = function(grid_id) stop('Always fails'),
+    getGridZ = function(grid_id) stop('Always fails'),
+    getGridNodeCount = function(grid_id) stop('Always fails'),
+    getGridEdgeCount = function(grid_id) stop('Always fails'),
+    getGridFaceCount = function(grid_id) stop('Always fails'),
+    getGridEdgeNodes = function(grid_id) stop('Always fails'),
+    getGridFaceNodes = function(grid_id) stop('Always fails'),
+    getGridFaceEdges = function(grid_id) stop('Always fails'),
+    getGridNodesPerFace = function(grid_id) stop('Always fails')
+  )
+)
diff --git a/test/fake_models.py b/test/fake_models.py
index 9656bc6..e5ac6cb 100644
--- a/test/fake_models.py
+++ b/test/fake_models.py
@@ -4,7 +4,7 @@
 import numpy as np
 from bmipy import Bmi
 
-from grpc4bmi.utils import GRPC_MAX_MESSAGE_LENGTH
+from grpc4bmi.constants import GRPC_MAX_MESSAGE_LENGTH
 
 
 class SomeException(Exception):
@@ -143,6 +143,9 @@ class GridModel(FailingModel):
     def __init__(self):
         super(GridModel, self).__init__(SomeException('not used'))
 
+    def initialize(self, filename):
+        pass
+
     def get_output_var_names(self) -> Tuple[str]:
         return 'plate_surface__temperature',
 
@@ -395,7 +398,7 @@ def __init__(self):
 
 
 class HugeModel(DTypeModel):
-    """Model which has value which does not fit in message body
+    """Model which has value which does not fit in single message body
 
     Can be run from command line with
 
@@ -409,3 +412,11 @@ def __init__(self):
         # Create value which is bigger than 4Mb
         dimension = (3 * GRPC_MAX_MESSAGE_LENGTH) // self.dtype.itemsize + 1000
         self.value = numpy.ones((dimension,), dtype=self.dtype)
+
+class WithItemSizeZeroAndVarTypeFloat32Model(Float32Model):
+    def get_var_itemsize(self, name):
+        return 0
+
+class WithItemSizeZeroAndUnknownVarType(WithItemSizeZeroAndVarTypeFloat32Model):
+    def get_var_type(self, name):
+        return 'real'
\ No newline at end of file
diff --git a/test/flatbmiheat.py b/test/flatbmiheat.py
deleted file mode 100644
index 658f8c6..0000000
--- a/test/flatbmiheat.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import numpy
-from heat import BmiHeat
-
-
-class FlatBmiHeat(BmiHeat):
-    """The BmiHeat returns multi dimensional arrays, but grpc interface uses flat arrays
-
-    """
-    def get_value_at_indices(self, var_name, dest, indices):
-        val = super(FlatBmiHeat, self).get_value_at_indices(var_name, indices).flatten()
-        numpy.copyto(src=val, dst=dest)
-        return dest
-
-    def set_value(self, var_name, src):
-        shape = super(FlatBmiHeat, self).get_grid_shape(super(FlatBmiHeat, self).get_var_grid(var_name))
-        reshaped = numpy.reshape(src, shape)
-        super(FlatBmiHeat, self).set_value(var_name, reshaped)
-
-    def get_value(self, var_name, dest):
-        val = super(FlatBmiHeat, self).get_value(var_name)
-        numpy.copyto(src=val.flatten(), dst=dest)
-        return dest
-
-    def get_grid_shape(self, grid_id, dest=None):
-        val = super(FlatBmiHeat, self).get_grid_shape(grid_id)
-        if dest is None:
-            return numpy.array(val)
-        numpy.copyto(src=val, dst=dest)
-        return dest
-
-    def get_grid_spacing(self, grid_id, dest):
-        val = super(FlatBmiHeat, self).get_grid_spacing(grid_id)
-        numpy.copyto(src=val, dst=dest)
-        return dest
-
-    def get_grid_origin(self, grid_id, dest):
-        val = super(FlatBmiHeat, self).get_grid_origin(grid_id)
-        numpy.copyto(src=val, dst=dest)
-        return dest
-
-
-class FlatLegacyBmiHeat(BmiHeat):
-    """BMI implementing interface defined in basic-modeling-interface 0.2
-
-    https://pypi.org/project/basic-modeling-interface/0.2/
-
-    """
-    def set_value(self, var_name, src):
-        shape = super(FlatLegacyBmiHeat, self).get_grid_shape(super(FlatLegacyBmiHeat, self).get_var_grid(var_name))
-        reshaped = numpy.reshape(src, shape)
-        super(FlatLegacyBmiHeat, self).set_value(var_name, reshaped)
-
-    def get_value(self, var_name):
-        val = super(FlatLegacyBmiHeat, self).get_value(var_name)
-        return val.flatten()
-
-    def get_value_at_indices(self, var_name, indices):
-        return super(FlatLegacyBmiHeat, self).get_value_at_indices(var_name, indices).flatten()
diff --git a/test/heat-images/README.md b/test/heat-images/README.md
new file mode 100644
index 0000000..f350128
--- /dev/null
+++ b/test/heat-images/README.md
@@ -0,0 +1,31 @@
+# Heat container images
+
+Heat is a simple model that solves the diffusion equation on a uniform rectangular plate with Dirichlet boundary conditions.
+
+Heat is implemented in [different languages](https://github.com/csdms?q=bmi-example&type=all&language=&sort=).
+
+This directory has the heat model wrapped up in a Docker container image with different combinations of
+
+- language
+- bmi version
+- grpc4bmi version
+- Python Protobuf version
+
+| Directory           | Language | BMI version | grpc4bmi version     | Python protobuf version |
+| ------------------- | -------- | ----------- | -------------------- | ----------------------- |
+| c-bmi20             | C        | 2.0         | 0.4.x                |                         |
+| cxx-bmi20           | C++      | 2.0         | 0.4.x                |                         |
+| python-bmi02        | Python   | 0.2         | 0.2.x                | 3                       |
+| python-bmi02-legacy | Python   | 0.2         | 0.4.x in legacy mode | 3                       |
+| python-bmi20        | Python   | 2.0         | 0.4.x                | 3                       |
+| python-bmi20-pb4    | Python   | 2.0         | 0.4.x                | 4                       |
+
+The images can be build with
+
+```shell
+docker compose build
+```
+
+The images can be tested using [heat_tester.py](heat_tester.py).
+
+To test Python Protobuf version client/server communication, the heat_tester.py (aka client) should be run with `protobuf==3 ` installed.
diff --git a/test/heat-images/c-bmi20/CMakeLists.txt b/test/heat-images/c-bmi20/CMakeLists.txt
new file mode 100644
index 0000000..d9bb030
--- /dev/null
+++ b/test/heat-images/c-bmi20/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.0)
+project(run_bmi_server C CXX)
+
+set(_cflags "-ansi -Wall -Wundef -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -O2")
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(BMIC REQUIRED IMPORTED_TARGET bmic)
+message("--   bmic include - ${BMIC_INCLUDE_DIRS}")
+include_directories(${BMIC_INCLUDE_DIRS})
+pkg_check_modules(GRPC4BMI REQUIRED grpc4bmi heatc)
+
+add_executable(run_bmi_server run_bmi_server.cc)
+
+target_include_directories(run_bmi_server PRIVATE)
+
+target_link_libraries(run_bmi_server
+                      ${GRPC4BMI_LINK_LIBRARIES}
+                      )
+
+install(TARGETS run_bmi_server
+        RUNTIME DESTINATION bin)
diff --git a/test/heat-images/c-bmi20/Dockerfile b/test/heat-images/c-bmi20/Dockerfile
new file mode 100644
index 0000000..d48a0a8
--- /dev/null
+++ b/test/heat-images/c-bmi20/Dockerfile
@@ -0,0 +1,80 @@
+# Build with
+#
+#  docker build -t heat:c-bmi20 -f test/heat-images/c-bmi20/Dockerfile .
+#
+# Run with
+#
+# ipython
+# from grpc4bmi.bmi_client_docker import BmiClientDocker
+# model = BmiClientDocker('heat:c-bmi20', work_dir='/tmp', delay=1)
+# model.get_component_name()
+# model.get_output_var_names()
+# from pathlib import Path
+# config = Path('/tmp/heat.config.txt')
+# config.write_text('1.5, 8.0, 7, 6')
+# model.initialize(str(config))
+# model.update()
+# import numpy as np
+# model.get_value('plate_surface__temperature', np.zeros(42))
+#
+FROM debian:buster AS builder
+
+# Install build deps
+RUN apt-get update && apt-get install -qy git build-essential cmake autoconf libtool pkg-config libssl-dev
+
+# Compile gRPC
+WORKDIR /opt/grpc
+ARG GRPC_VERSION=v1.51.1
+RUN echo ${GRPC_VERSION}
+RUN git clone -b ${GRPC_VERSION} --depth 1 https://github.com/grpc/grpc . && git submodule update --init
+WORKDIR /opt/grpc/cmake/build
+RUN cmake -DgRPC_INSTALL=ON  -DgRPC_SSL_PROVIDER=package -DgRPC_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=ON ../.. \
+    && make -j6 && make install && ldconfig
+
+# Install bmi-cxx
+ARG BMICXX_VERSION=v2.0
+RUN git clone -b ${BMICXX_VERSION} https://github.com/csdms/bmi-cxx.git /opt/bmi-cxx
+WORKDIR /opt/bmi-cxx/build
+RUN cmake .. && make install
+
+# Install bmi-c
+ARG BMIC_VERSION=v2.1
+RUN git clone -b ${BMIC_VERSION} https://github.com/csdms/bmi-c.git /opt/bmi-c
+WORKDIR /opt/bmi-c/build
+RUN cmake .. && make install
+
+# Install heat-c
+ARG HEAT_VERSION=v2.0.1
+RUN git clone -b ${HEAT_VERSION} https://github.com/csdms/bmi-example-c /opt/bmi-example-c
+WORKDIR /opt/bmi-example-c/build
+RUN cmake .. && make install && ldconfig
+
+# Install grpc4bmi, use commit c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66
+# head of bmi2 branch, PR https://github.com/eWaterCycle/grpc4bmi/pull/124
+ARG GRPC4BMI_VERSION=c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66
+RUN git clone https://github.com/eWaterCycle/grpc4bmi /opt/grpc4bmi \
+  && cd /opt/grpc4bmi && git checkout ${GRPC4BMI_VERSION}
+WORKDIR /opt/grpc4bmi/cpp/build
+RUN cmake .. && make install
+
+# Compile main
+WORKDIR /opt/grpc4bmiheatc/build
+COPY test/heat-images/c-bmi20/run_bmi_server.cc /opt/grpc4bmiheatc
+COPY test/heat-images/c-bmi20/CMakeLists.txt /opt/grpc4bmiheatc
+RUN cmake .. && make install
+
+# run container
+FROM debian:buster
+
+# Install runtime deps
+RUN apt-get update && apt-get install -qy libssl1.1 && rm -rf /var/lib/apt/lists/*
+
+# Copy compiled and deps
+COPY --from=builder /usr/local/bin/run_bmi_server /usr/local/bin/run_bmi_server
+COPY --from=builder /usr/local/lib/ /usr/local/lib/
+
+RUN ldconfig
+
+ENV BMI_PORT=50051
+
+ENTRYPOINT ["/usr/local/bin/run_bmi_server"]
diff --git a/test/heat-images/c-bmi20/run_bmi_server.cc b/test/heat-images/c-bmi20/run_bmi_server.cc
new file mode 100644
index 0000000..03d12c6
--- /dev/null
+++ b/test/heat-images/c-bmi20/run_bmi_server.cc
@@ -0,0 +1,11 @@
+#include "bmi_grpc_server.h"
+#include <bmi.h>
+#include "bmi_heat.h"
+
+int main(int argc, char* argv[])
+{
+    Bmi *model = (Bmi *) malloc(sizeof(Bmi));
+    register_bmi_heat(model);
+    run_bmi_server(model, argc, argv);
+    return 0;
+}
\ No newline at end of file
diff --git a/test/heat-images/cxx-bmi20/CMakeLists.txt b/test/heat-images/cxx-bmi20/CMakeLists.txt
new file mode 100644
index 0000000..e42f697
--- /dev/null
+++ b/test/heat-images/cxx-bmi20/CMakeLists.txt
@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.0)
+project(run_bmi_server CXX)
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(BMICXX REQUIRED IMPORTED_TARGET bmicxx)
+message("--   bmicxx include - ${BMICXX_INCLUDE_DIRS}")
+include_directories(${BMICXX_INCLUDE_DIRS})
+pkg_check_modules(GRPC4BMI REQUIRED grpc4bmi heatcxx)
+
+add_executable(run_bmi_server run_bmi_server.cc)
+
+target_include_directories(run_bmi_server PRIVATE)
+
+target_link_libraries(run_bmi_server
+                      ${GRPC4BMI_LINK_LIBRARIES}
+                      )
+
+install(TARGETS run_bmi_server
+        RUNTIME DESTINATION bin)
diff --git a/test/heat-images/cxx-bmi20/Dockerfile b/test/heat-images/cxx-bmi20/Dockerfile
new file mode 100644
index 0000000..9f51643
--- /dev/null
+++ b/test/heat-images/cxx-bmi20/Dockerfile
@@ -0,0 +1,81 @@
+# Build with
+#
+#  docker build -t heat:cxx-bmi20 -f test/heat-images/cxx-bmi20/Dockerfile .
+#
+# Run with
+#
+# ipython
+# from grpc4bmi.bmi_client_docker import BmiClientDocker
+# model = BmiClientDocker('heat:cxx-bmi20', work_dir='/tmp', delay=1)
+# model.get_component_name()
+# model.get_output_var_names()
+# from pathlib import Path
+# config = Path('/tmp/heat.config.txt')
+# config.write_text('1.5, 8.0, 7, 6')
+# model.initialize(str(config))
+# model.update()
+# import numpy as np
+# model.get_value('plate_surface__temperature', np.zeros(42))
+#
+FROM debian:buster AS builder
+
+# Install build deps
+RUN apt-get update && apt-get install -qy git build-essential cmake autoconf libtool pkg-config libssl-dev
+
+# Compile gRPC
+WORKDIR /opt/grpc
+ARG GRPC_VERSION=v1.51.1
+RUN echo ${GRPC_VERSION}
+RUN git clone -b ${GRPC_VERSION} --depth 1 https://github.com/grpc/grpc . && git submodule update --init
+WORKDIR /opt/grpc/cmake/build
+RUN cmake -DgRPC_INSTALL=ON  -DgRPC_SSL_PROVIDER=package -DgRPC_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=ON ../.. \
+    && make -j6 && make install && ldconfig
+
+# Install bmi-cxx
+ARG BMICXX_VERSION=v2.0
+RUN git clone -b ${BMICXX_VERSION} https://github.com/csdms/bmi-cxx.git /opt/bmi-cxx
+WORKDIR /opt/bmi-cxx/build
+RUN cmake .. && make install
+
+# Install bmi-c
+ARG BMIC_VERSION=v2.1
+RUN git clone -b ${BMIC_VERSION} https://github.com/csdms/bmi-c.git /opt/bmi-c
+WORKDIR /opt/bmi-c/build
+RUN cmake .. && make install
+
+# Install heat-cxx
+ARG HEAT_VERSION=v2.1
+RUN git clone -b ${HEAT_VERSION} https://github.com/csdms/bmi-example-cxx /opt/bmi-example-cxx
+WORKDIR /opt/bmi-example-cxx/build
+RUN cmake .. && make install && ldconfig
+
+# Install grpc4bmi, use commit cf9928f6ba01309166ffd2e9f66b29996bdcb0e5
+# head of latest-protobuf branch, PR https://github.com/eWaterCycle/grpc4bmi/pull/126
+ARG GRPC4BMI_VERSION=cf9928f6ba01309166ffd2e9f66b29996bdcb0e5
+RUN git clone https://github.com/eWaterCycle/grpc4bmi /opt/grpc4bmi \
+  && cd /opt/grpc4bmi && git checkout ${GRPC4BMI_VERSION}
+
+WORKDIR /opt/grpc4bmi/cpp/build
+RUN cmake .. && make install
+
+# Compile main
+WORKDIR /opt/grpc4bmiheatcxx/build
+COPY test/heat-images/cxx-bmi20/run_bmi_server.cc /opt/grpc4bmiheatcxx
+COPY test/heat-images/cxx-bmi20/CMakeLists.txt /opt/grpc4bmiheatcxx
+RUN cmake .. && make install
+
+# run container
+FROM debian:buster
+
+# Install runtime deps
+RUN apt-get update && apt-get install -qy libssl1.1 && rm -rf /var/lib/apt/lists/*
+
+# Copy compiled and deps
+COPY --from=builder /usr/local/bin/run_bmi_server /usr/local/bin/run_bmi_server
+COPY --from=builder /usr/local/lib/ /usr/local/lib/
+
+RUN ldconfig
+
+ENV BMI_PORT=50051
+
+ENTRYPOINT ["/usr/local/bin/run_bmi_server"]
diff --git a/test/heat-images/cxx-bmi20/run_bmi_server.cc b/test/heat-images/cxx-bmi20/run_bmi_server.cc
new file mode 100644
index 0000000..03a2bd9
--- /dev/null
+++ b/test/heat-images/cxx-bmi20/run_bmi_server.cc
@@ -0,0 +1,10 @@
+#include "bmi_grpc_server.h"
+#include <bmi.hxx>
+#include "bmi_heat.hxx"
+
+int main(int argc, char* argv[])
+{
+    bmi::Bmi* model = new BmiHeat();
+    run_bmi_server(model, argc, argv);
+    return 0;
+}
\ No newline at end of file
diff --git a/test/heat-images/docker-compose.yaml b/test/heat-images/docker-compose.yaml
new file mode 100644
index 0000000..942c149
--- /dev/null
+++ b/test/heat-images/docker-compose.yaml
@@ -0,0 +1,32 @@
+version: "3.9"
+services:
+  c-bmi20:
+    image: heat:c-bmi20
+    build: 
+      context: ../..
+      dockerfile: test/heat-images/c-bmi20/Dockerfile
+  cxx-bmi20:
+    image: heat:cxx-bmi20
+    build: 
+      context: ../..
+      dockerfile: test/heat-images/cxx-bmi20/Dockerfile
+  python-bmi02:
+    image: heat:py-bmi02
+    build: 
+      context: ../..
+      dockerfile: test/heat-images/python-bmi02/Dockerfile
+  python-bmi02-legacy:
+    image: heat:py-0.2-legacy
+    build: 
+      context: ../..
+      dockerfile: test/heat-images/python-bmi02-legacy/Dockerfile
+  python-bmi20:
+    image: heat:py-2.0
+    build: 
+      context: ../..
+      dockerfile: test/heat-images/python-bmi20/Dockerfile
+  python-bmi20-pb4:
+    image: heat:py-2.0-pb4
+    build: 
+      context: ../..
+      dockerfile: test/heat-images/python-bmi20-pb4/Dockerfile
diff --git a/test/heat-images/heat_tester.py b/test/heat-images/heat_tester.py
new file mode 100644
index 0000000..e04e5f5
--- /dev/null
+++ b/test/heat-images/heat_tester.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+from textwrap import dedent
+
+from grpc4bmi.bmi_client_docker import BmiClientDocker
+from grpc4bmi.bmi_optionaldest import OptionalDestBmi
+
+def test_heat(tmp_path: Path):
+    # Config file for Python based heat model
+    py_config = dedent("""\
+        shape:
+        - 6
+        - 8
+        spacing:
+        - 1.0
+        - 1.0
+        origin:
+        - 0.0
+        - 0.0
+        alpha: 1.0
+    """)
+    # Config file for C/C++ based heat model
+    c_config = '1.5, 8.0, 7, 6'
+
+
+    image, config_body = (
+        # Comment out line you want to run
+    'heat:py-0.2',py_config,
+    # 'heat:py-bmi02', py_config,
+    # 'heat:py-0.2-legacy',py_config
+    # 'heat:py-2.0',py_config
+    # 'heat:py-2.0-pb4',py_config
+    # 'heat:cxx-bmi20', c_config
+    # 'heat:c-bmi20', c_config
+    )
+
+    config = tmp_path / 'config.yaml'
+    config.write_text(config_body)
+
+    print(image)
+    dmodel = BmiClientDocker(image, work_dir=str(tmp_path), delay=1)
+    model = OptionalDestBmi(dmodel)
+
+    model.initialize(str(config))
+
+    print(model.get_component_name())
+    print(model.get_output_var_names())
+
+    model.update()
+
+    var_name = 'plate_surface__temperature'
+    grid_id = model.get_var_grid(var_name)
+    print(model.get_grid_shape(grid_id))
+
+    values = model.get_value(var_name)
+    print(values)
+
+    model.finalize()
+    # assert 1==0
+
+test_heat(tmp_path=Path('/tmp'))
diff --git a/test/heat-images/python-bmi02-legacy/Dockerfile b/test/heat-images/python-bmi02-legacy/Dockerfile
new file mode 100644
index 0000000..7dfeee1
--- /dev/null
+++ b/test/heat-images/python-bmi02-legacy/Dockerfile
@@ -0,0 +1,24 @@
+# Python heat model with BMI v0.2, grpc4bmi==bmi2 and pb3
+#
+# Build with
+#
+#   docker build -t heat:py-0.2-legacy test/heat-images/python-bmi02-legacy
+#
+#
+# Run with
+#
+#   from grpc4bmi.bmi_client_docker import BmiClientDocker
+#   model = BmiClientDocker('heat:py-0.2-legacy', work_dir='/tmp', delay=1)
+#
+
+FROM python:3.10-buster
+
+RUN pip install numpy 'scipy==1.8.1' 'PyYAML<6'
+
+RUN pip install https://github.com/csdms/bmi-example-python/archive/refs/tags/v0.1.zip
+
+# Install grpc4bmi, use commit c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66
+# head of bmi2 branch, PR https://github.com/eWaterCycle/grpc4bmi/pull/124
+RUN pip install https://github.com/eWaterCycle/grpc4bmi/archive/c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66.zip
+
+ENTRYPOINT ["run-bmi-server","--bmi-version", "0.2", "--name", "heat.BmiHeat", "--port", "50051"]
diff --git a/test/heat-images/python-bmi02/Dockerfile b/test/heat-images/python-bmi02/Dockerfile
new file mode 100644
index 0000000..7eeaa7a
--- /dev/null
+++ b/test/heat-images/python-bmi02/Dockerfile
@@ -0,0 +1,29 @@
+# Python heat model with BMI v0.2, grpc4bmi==0.2 and pb3
+#
+# Build with
+#
+#   docker build -t heat:py-0.2 test/heat-images/python-bmi02
+#
+#
+# Run with
+
+#   from grpc4bmi.bmi_client_docker import BmiClientDocker
+#   model = BmiClientDocker('heat:py-0.2', work_dir='/tmp', delay=1)
+#
+
+# docker run -ti --entrypoint pip heat:py-0.2 list |grep grpc
+# grpc4bmi                 0.2.16
+# grpcio                   1.51.1
+# grpcio-reflection        1.48.2
+# docker run -ti --entrypoint pip heat:py-0.2 list |grep proto
+# protobuf                 3.20.3
+
+FROM python:3.10-buster
+
+RUN pip install numpy 'scipy==1.8.1' 'PyYAML<6'
+
+RUN pip install https://github.com/csdms/bmi-example-python/archive/refs/tags/v0.1.zip
+
+RUN pip install grpc4bmi==0.2.16
+
+ENTRYPOINT ["run-bmi-server", "--name", "heat.BmiHeat", "--port", "50051"]
diff --git a/test/heat-images/python-bmi20-pb4/Dockerfile b/test/heat-images/python-bmi20-pb4/Dockerfile
new file mode 100644
index 0000000..62fe3f7
--- /dev/null
+++ b/test/heat-images/python-bmi20-pb4/Dockerfile
@@ -0,0 +1,30 @@
+# Python heat model with BMI v2.0
+#
+# Build with
+#
+#   docker build -t heat:py-2.0-pb4 test/heat-images/python-bmi20-pb4
+#
+#
+# Run with
+
+#   from grpc4bmi.bmi_client_docker import BmiClientDocker
+#   model = BmiClientDocker('heat:py-2.0-pb4', work_dir='/tmp', delay=1)
+#
+# docker run -ti --entrypoint pip heat:py-2.0 list |grep grpc
+# grpc4bmi                 0.3.2
+# grpcio                   1.51.1
+# grpcio-reflection        1.48.2
+# grpcio-status            1.48.2
+# docker run -ti --entrypoint pip heat:py-2.0 list |grep proto
+# googleapis-common-protos 1.58.0
+# protobuf                 3.20.3
+
+FROM python:3.10-buster
+
+RUN pip install https://github.com/csdms/bmi-example-python/archive/44c7d4c54f7fa4c17f51f86a645ba3e2633a4dfb.zip
+
+# Install grpc4bmi, use commit cf9928f6ba01309166ffd2e9f66b29996bdcb0e5
+# head of latest-protobuf branch, PR https://github.com/eWaterCycle/grpc4bmi/pull/126
+RUN pip install https://github.com/eWaterCycle/grpc4bmi/archive/cf9928f6ba01309166ffd2e9f66b29996bdcb0e5.zip
+
+ENTRYPOINT ["run-bmi-server", "--name", "heat.BmiHeat", "--port", "50051"]
diff --git a/test/heat-images/python-bmi20/Dockerfile b/test/heat-images/python-bmi20/Dockerfile
new file mode 100644
index 0000000..0aea730
--- /dev/null
+++ b/test/heat-images/python-bmi20/Dockerfile
@@ -0,0 +1,30 @@
+# Python heat model with BMI v2.0 and pb3
+#
+# Build with
+#
+#   docker build -t heat:py-2.0 test/heat-images/python-bmi20
+#
+#
+# Run with
+
+#   from grpc4bmi.bmi_client_docker import BmiClientDocker
+#   model = BmiClientDocker('heat:py-2.0', work_dir='/tmp', delay=1)
+#
+# docker run -ti --entrypoint pip heat:py-2.0 list |grep grpc
+# grpc4bmi                 0.3.2
+# grpcio                   1.51.1
+# grpcio-reflection        1.48.2
+# grpcio-status            1.48.2
+# docker run -ti --entrypoint pip heat:py-2.0 list |grep proto
+# googleapis-common-protos 1.58.0
+# protobuf                 3.20.3
+
+FROM python:3.10-buster
+
+RUN pip install https://github.com/csdms/bmi-example-python/archive/44c7d4c54f7fa4c17f51f86a645ba3e2633a4dfb.zip
+
+# Install grpc4bmi, use commit c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66
+# head of bmi2 branch, PR https://github.com/eWaterCycle/grpc4bmi/pull/124
+RUN pip install https://github.com/eWaterCycle/grpc4bmi/archive/c5cc9b9bf33b6043e8db242a07c6fb92b9c63f66.zip
+
+ENTRYPOINT ["run-bmi-server", "--name", "heat.BmiHeat", "--port", "50051"]
diff --git a/test/legacybmiheat.py b/test/legacybmiheat.py
new file mode 100644
index 0000000..ed47857
--- /dev/null
+++ b/test/legacybmiheat.py
@@ -0,0 +1,35 @@
+import numpy as np
+from heat import BmiHeat
+
+from grpc4bmi.reserve import reserve_grid_padding, reserve_grid_shape, reserve_values, reserve_values_at_indices
+
+class LegacyBmiHeat(BmiHeat):
+    """BMI implementing interface defined in basic-modeling-interface 0.2
+
+    https://pypi.org/project/basic-modeling-interface/0.2/
+
+    """
+    def get_value(self, var_name):
+        dest = reserve_values(self, var_name)
+        super(LegacyBmiHeat, self).get_value(var_name, dest)
+        return dest
+
+    def get_value_at_indices(self, var_name, indices):
+        dest = reserve_values_at_indices(self, var_name, indices)
+        super(LegacyBmiHeat, self).get_value_at_indices(var_name, dest, indices)
+        return dest
+
+    def get_grid_shape(self, grid):
+        dest = reserve_grid_shape(self, grid)
+        super(LegacyBmiHeat, self).get_grid_shape(grid, dest)
+        return dest
+
+    def get_grid_spacing(self, grid):
+        dest = reserve_grid_padding(self, grid)
+        super(LegacyBmiHeat, self).get_grid_spacing(grid, dest)
+        return dest
+
+    def get_grid_origin(self, grid):
+        dest = reserve_grid_padding(self, grid)
+        super(LegacyBmiHeat, self).get_grid_origin(grid, dest)
+        return dest
diff --git a/test/test_apptainer.py b/test/test_apptainer.py
new file mode 100644
index 0000000..cd5ea5e
--- /dev/null
+++ b/test/test_apptainer.py
@@ -0,0 +1,105 @@
+import os
+from typing import Type
+
+import pytest
+from typeguard import TypeCheckError
+
+from grpc4bmi.bmi_client_apptainer import SUPPORTED_APPTAINER_VERSIONS, BmiClientApptainer, check_apptainer_version_string
+from grpc4bmi.exceptions import ApptainerVersionException, DeadContainerException
+
+class Test_check_apptainer_version_string:
+    @pytest.mark.parametrize("test_input", [
+        ('apptainer version 1.0.0-rc.2'),  
+        ('apptainer version 1.0.0'),
+        ('apptainer version 1.0.3'),
+        ('apptainer version 1.1.0-rc.3'),
+        ('apptainer version 1.1.2'),
+    ])
+    def test_ok(self, test_input: str):
+        result = check_apptainer_version_string(test_input)
+        assert result
+
+    @pytest.mark.parametrize("test_input,error_class,expected", [
+        ('apptainer version 1.0.0-rc.1', ApptainerVersionException, SUPPORTED_APPTAINER_VERSIONS),
+        ('apptainer version 0.1.0', ApptainerVersionException, SUPPORTED_APPTAINER_VERSIONS),
+    ])
+    def test_too_old(self, test_input: str, error_class: Type[ApptainerVersionException], expected: str):
+        with pytest.raises(error_class, match=expected):
+            check_apptainer_version_string(test_input)
+
+IMAGE_NAME = "docker://ewatercycle/walrus-grpc4bmi:v0.2.0"
+
+@pytest.fixture
+def walrus_model(tmp_path, walrus_input):
+    model = BmiClientApptainer(image=IMAGE_NAME, work_dir=str(tmp_path))
+    yield model
+    del model
+
+class TestBmiClientApptainerHappyDays:
+    def test_component_name(self, walrus_model):
+        assert walrus_model.get_component_name() == 'WALRUS'
+
+class TestBmiClientApptainerBadDays:
+    def test_get_value_ref(self, walrus_model):
+        with pytest.raises(NotImplementedError):
+            walrus_model.get_value_ref('Q')
+
+    def test_inputdir_absent(self, tmp_path):
+        dirthatdoesnotexist = 'dirthatdoesnotexist'
+        input_dir = tmp_path / dirthatdoesnotexist
+        work_dir = tmp_path / 'work'
+        work_dir.mkdir()
+        with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist):
+            BmiClientApptainer(image=IMAGE_NAME, work_dir=str(work_dir), input_dirs=[str(input_dir)])
+
+    def test_workdir_absent(self, tmp_path):
+        dirthatdoesnotexist = 'dirthatdoesnotexist'
+        work_dir = tmp_path / dirthatdoesnotexist
+        with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist):
+            BmiClientApptainer(image=IMAGE_NAME, work_dir=str(work_dir))
+
+    def test_same_inputdir_and_workdir(self, tmp_path):
+        some_dir = str(tmp_path)
+        match = 'Found work_dir equal to one of the input directories. Please drop that input dir.'
+        with pytest.raises(ValueError, match=match):
+            BmiClientApptainer(image=IMAGE_NAME, input_dirs=(some_dir,), work_dir=some_dir)
+
+    def test_workdir_as_number(self):
+        with pytest.raises(TypeCheckError, match='is not an instance of str'):
+            BmiClientApptainer(image=IMAGE_NAME, work_dir=42)
+
+    def test_inputdirs_as_str(self, tmp_path):
+        some_dir = str(tmp_path)
+        with pytest.raises(TypeError, match=' must be collections.abc.Iterable'):
+            BmiClientApptainer(image=IMAGE_NAME, input_dirs='old type', work_dir=some_dir)
+
+    def test_inputdirs_as_number(self, tmp_path):
+        some_dir = str(tmp_path)
+        with pytest.raises(TypeCheckError, match='is not an instance of collections.abc.Iterable'):
+            BmiClientApptainer(image=IMAGE_NAME, input_dirs=42, work_dir=some_dir)
+
+
+
+class TestRedirectOutput:
+    EXPECTED = 'Hello from Docker!'
+
+    @pytest.fixture(scope="module")
+    def image(self):
+        hello_image = 'docker://hello-world'
+        # Cache image, first test does not use delay time to build image
+        os.system(f'apptainer run {hello_image}')
+        return hello_image
+
+    def test_default(self, image, tmp_path, capfd):
+        with pytest.raises(DeadContainerException) as excinf:
+            BmiClientApptainer(image=image, work_dir=str(tmp_path), delay=2)
+
+        assert self.EXPECTED not in capfd.readouterr().out
+        assert self.EXPECTED in excinf.value.logs
+
+    def test_devnull(self, image, tmp_path, capfd):
+        with pytest.raises(DeadContainerException) as excinf:
+            BmiClientApptainer(image=image, work_dir=str(tmp_path), capture_logs=False, delay=2)
+
+        assert self.EXPECTED not in capfd.readouterr().out
+        assert self.EXPECTED not in excinf.value.logs
diff --git a/test/test_client.py b/test/test_client.py
index 78d6fdb..26419a4 100644
--- a/test/test_client.py
+++ b/test/test_client.py
@@ -8,14 +8,15 @@
 from google.protobuf import any_pb2
 from google.rpc import error_details_pb2, status_pb2, code_pb2
 from grpc_status import rpc_status
+from heat import BmiHeat
+from typeguard import TypeCheckError
 
 from grpc4bmi.bmi_grpc_server import BmiServer
 from grpc4bmi.bmi_grpc_client import BmiClient, RemoteException, handle_error
 from grpc4bmi.reserve import reserve_values, reserve_grid_shape, reserve_grid_padding
 from test.fake_models import SomeException, FailingModel, Rect3DGridModel, UnstructuredGridBmiModel, UniRectGridModel, \
     Rect2DGridModel, Structured3DQuadrilateralsGridModel, Structured2DQuadrilateralsGridModel, Float32Model, Int32Model, \
-    BooleanModel
-from test.flatbmiheat import FlatBmiHeat
+    BooleanModel, WithItemSizeZeroAndUnknownVarType, WithItemSizeZeroAndVarTypeFloat32Model
 
 logging.basicConfig(level=logging.DEBUG)
 
@@ -52,8 +53,8 @@ def add_context(*args, **kwargs):
 
 
 def make_bmi_classes(init=False):
-    client = BmiClient(stub=ServerWrapper(BmiServer(FlatBmiHeat())))
-    local = FlatBmiHeat()
+    client = BmiClient(stub=ServerWrapper(BmiServer(BmiHeat())))
+    local = BmiHeat()
     if init:
         numpy.random.seed(0)
         client.initialize(None)
@@ -105,6 +106,15 @@ def test_initialize():
     del client
 
 
+def test_initialize_with_nonstring():
+    client, local = make_bmi_classes(False)
+    assert client is not None
+    with pytest.raises(TypeCheckError, match='did not match any element in the union'):
+        client.initialize(42)
+    client.finalize()
+    del client
+
+
 def test_update():
     client, local = make_bmi_classes(True)
     client.update()
@@ -554,7 +564,7 @@ def test_get_grid_face_count(self, bmiclient):
         assert result == 3
 
     def test_get_grid_edge_nodes(self, bmiclient):
-        placeholder = numpy.empty(16, dtype=numpy.int)
+        placeholder = numpy.empty(16, dtype=int)
 
         result = bmiclient.get_grid_edge_nodes(0, placeholder)
 
@@ -562,7 +572,7 @@ def test_get_grid_edge_nodes(self, bmiclient):
         numpy.testing.assert_allclose(result, expected)
 
     def test_grid_face_nodes(self, bmiclient):
-        placeholder = numpy.empty(11, dtype=numpy.int)
+        placeholder = numpy.empty(11, dtype=int)
 
         result = bmiclient.get_grid_face_nodes(0, placeholder)
 
@@ -570,7 +580,7 @@ def test_grid_face_nodes(self, bmiclient):
         numpy.testing.assert_allclose(result, expected)
 
     def test_grid_face_edges(self, bmiclient):
-        placeholder = numpy.empty(11, dtype=numpy.int)
+        placeholder = numpy.empty(11, dtype=int)
 
         result = bmiclient.get_grid_face_edges(0, placeholder)
 
@@ -578,7 +588,7 @@ def test_grid_face_edges(self, bmiclient):
         numpy.testing.assert_allclose(result, expected)
 
     def test_grid_nodes_per_face(self, bmiclient):
-        placeholder = numpy.empty(3, dtype=numpy.int)
+        placeholder = numpy.empty(3, dtype=int)
 
         result = bmiclient.get_grid_nodes_per_face(0, placeholder)
 
@@ -701,16 +711,16 @@ def test_get_value(self, bmiclient):
 
     def test_get_value_at_indices(self, bmiclient):
         with pytest.raises(MyRpcError):
-            bmiclient.get_value_at_indices(self.name, numpy.empty(1, dtype=numpy.bool), numpy.array([1]))
+            bmiclient.get_value_at_indices(self.name, numpy.empty(1, dtype=numpy.bool_), numpy.array([1]))
 
     def test_set_value(self, bmiclient):
-        value = numpy.array((False, False, False), dtype=numpy.bool)
+        value = numpy.array((False, False, False), dtype=numpy.bool_)
 
         with pytest.raises(NotImplementedError):
             bmiclient.set_value(self.name, value)
 
     def test_set_value_at_indices(self, bmiclient):
-        value = numpy.array([False], dtype=numpy.bool)
+        value = numpy.array([False], dtype=numpy.bool_)
 
         with pytest.raises(NotImplementedError):
             bmiclient.set_value_at_indices(self.name, numpy.array([1]), value)
@@ -822,3 +832,43 @@ def test_same_port_twice(self):
         with BmiClient.create_grpc_channel(port) as channel1, BmiClient.create_grpc_channel(port) as channel2:
             assert channel1._channel.target() == b'localhost:51235'
             assert channel2._channel.target() == b'localhost:51235'
+
+class TestModelWithItemSizeZeroAndVarTypeFloat32:
+    name = 'plate_surface__temperature'
+
+    @pytest.fixture
+    def bmimodel(self):
+        model = WithItemSizeZeroAndVarTypeFloat32Model()
+        yield model
+        del model
+
+    @pytest.fixture
+    def bmiclient(self, bmimodel):
+        client = BmiClient(stub=ServerWrapper(BmiServer(bmimodel)))
+        yield client
+        del client
+
+    def test_get_var_itemsize(self, bmiclient):
+        result = bmiclient.get_var_itemsize(self.name)
+
+        expected = 4
+        assert result == expected
+
+class TestModelWithItemSizeZeroAndUnknownVarType:
+    name = 'plate_surface__temperature'
+
+    @pytest.fixture
+    def bmimodel(self):
+        model = WithItemSizeZeroAndUnknownVarType()
+        yield model
+        del model
+
+    @pytest.fixture
+    def bmiclient(self, bmimodel):
+        client = BmiClient(stub=ServerWrapper(BmiServer(bmimodel)))
+        yield client
+        del client
+
+    def test_get_var_itemsize(self, bmiclient):
+        with pytest.raises(ValueError, match='get_var_itemsize returned 0, which is impossible'):
+            bmiclient.get_var_itemsize(self.name)
diff --git a/test/test_docker.py b/test/test_docker.py
index f95dff8..812ec21 100644
--- a/test/test_docker.py
+++ b/test/test_docker.py
@@ -1,28 +1,20 @@
 from io import BytesIO
 
 import docker
+import numpy as np
 import pytest
+from typeguard import TypeCheckError
 
-from grpc4bmi.bmi_client_docker import BmiClientDocker, DeadDockerContainerException
-from grpc4bmi.reserve import reserve_grid_padding, reserve_values
+from grpc4bmi.bmi_client_docker import BmiClientDocker
+from grpc4bmi.exceptions import DeadContainerException
+from grpc4bmi.reserve import reserve_grid_padding
 
 walrus_docker_image = 'ewatercycle/walrus-grpc4bmi:v0.3.1'
 
 
 @pytest.fixture()
 def walrus_model(tmp_path, walrus_input):
-    model = BmiClientDocker(image=walrus_docker_image, image_port=55555, input_dir=str(tmp_path))
-    yield model
-    del model
-
-
-@pytest.fixture()
-def walrus_model_with_extra_volume(tmp_path, walrus_input_on_extra_volume):
-    (input_dir, extra_volumes) = walrus_input_on_extra_volume
-    model = BmiClientDocker(image=walrus_docker_image,
-                            image_port=55555,
-                            input_dir=str(input_dir),
-                            extra_volumes=extra_volumes)
+    model = BmiClientDocker(image=walrus_docker_image, image_port=55555, work_dir=str(tmp_path))
     yield model
     del model
 
@@ -37,6 +29,19 @@ def exit_container():
     client.images.remove(image.id, force=True)
 
 
+@pytest.fixture()
+def walrus_model_with_2input_dirs(tmp_path, walrus_2input_dirs):
+    input_dirs = walrus_2input_dirs['input_dirs']
+    work_dir = tmp_path / 'work'
+    work_dir.mkdir()
+    model = BmiClientDocker(image="ewatercycle/walrus-grpc4bmi:v0.2.0",
+                            image_port=55555,
+                            work_dir=str(work_dir),
+                            input_dirs=input_dirs)
+    yield model
+    del model
+
+
 class TestBmiClientDocker:
     def test_component_name(self, walrus_model):
         assert walrus_model.get_component_name() == 'WALRUS'
@@ -56,32 +61,63 @@ def test_get_value_ptr(self, walrus_model):
         with pytest.raises(NotImplementedError):
             walrus_model.get_value_ptr('Q')
 
-    def test_extra_volume(self, walrus_model_with_extra_volume):
-        walrus_model_with_extra_volume.initialize('/data/input/config.yml')
-        walrus_model_with_extra_volume.update()
-
-        # After initialization and update the forcings have been read from the extra volume
-        result = reserve_values(walrus_model_with_extra_volume, 'Q')
-        assert len(walrus_model_with_extra_volume.get_value('Q', result)) == 1
-
     def test_get_var_location(self, walrus_model):
         assert walrus_model.get_var_location('Q') == 'node'
 
-    def test_logs(self, walrus_model):
-        lg = walrus_model.logs()
-        assert b"Loading required package:" in lg
+    def test_2input_dirs(self, walrus_2input_dirs, walrus_model_with_2input_dirs):
+        config_file = walrus_2input_dirs['cfg']
+        walrus_model_with_2input_dirs.initialize(config_file)
+        walrus_model_with_2input_dirs.update()
+        # After initialization and update the forcings have been read from the extra volume
+        val = walrus_model_with_2input_dirs.get_value('Q', np.zeros((1,)))
+        assert len(val) == 1
 
     def test_inputdir_absent(self, tmp_path):
         dirthatdoesnotexist = 'dirthatdoesnotexist'
         input_dir = tmp_path / dirthatdoesnotexist
+        work_dir = tmp_path / 'work'
+        work_dir.mkdir()
+        with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist):
+            BmiClientDocker(image=walrus_docker_image, image_port=55555,
+                            work_dir=str(work_dir), input_dirs=[str(input_dir)])
+
+    def test_workdir_absent(self, tmp_path):
+        dirthatdoesnotexist = 'dirthatdoesnotexist'
+        work_dir = tmp_path / dirthatdoesnotexist
         with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist):
-            BmiClientDocker(image=walrus_docker_image, image_port=55555, input_dir=str(input_dir))
+            BmiClientDocker(image=walrus_docker_image, image_port=55555, work_dir=str(work_dir))
 
-    def test_container_start_failure(self, exit_container):
+    def test_container_start_failure(self, exit_container, tmp_path):
         expected = r"Failed to start Docker container with image"
-        with pytest.raises(DeadDockerContainerException, match=expected) as excinfo:
-            BmiClientDocker(image=exit_container)
+        with pytest.raises(DeadContainerException, match=expected) as excinfo:
+            BmiClientDocker(image=exit_container, work_dir=str(tmp_path))
 
         assert excinfo.value.exitcode == 25
-        assert b'my stderr' in excinfo.value.logs
-        assert b'my stdout' in excinfo.value.logs
+        assert 'my stderr' in excinfo.value.logs
+        assert 'my stdout' in excinfo.value.logs
+
+    def test_same_inputdir_and_workdir(self, tmp_path):
+        some_dir = str(tmp_path)
+        match = 'Found work_dir equal to one of the input directories. Please drop that input dir.'
+        with pytest.raises(ValueError, match=match):
+            BmiClientDocker(image=walrus_docker_image, image_port=55555, input_dirs=(some_dir,), work_dir=some_dir)
+
+    def test_workdir_as_number(self):
+        with pytest.raises(TypeCheckError, match='is not an instance of str'):
+            BmiClientDocker(image=walrus_docker_image, work_dir=42)
+
+    def test_inputdirs_as_str(self, tmp_path):
+        some_dir = str(tmp_path)
+        with pytest.raises(TypeError, match='must be collections.abc.Iterable'):
+            BmiClientDocker(image=walrus_docker_image, input_dirs='old type', work_dir=some_dir)
+
+    def test_inputdirs_as_number(self, tmp_path):
+        some_dir = str(tmp_path)
+        with pytest.raises(TypeCheckError, match='is not an instance of collections.abc.Iterable'):
+            BmiClientDocker(image=walrus_docker_image, input_dirs=42, work_dir=some_dir)
+
+    def test_logs(self, walrus_model, capfd):
+        logs = walrus_model.logs()
+
+        assert 'R[write to console]' in logs
+        assert 'R[write to console]' not in capfd.readouterr().out
diff --git a/test/test_legacy_server.py b/test/test_legacy_server.py
index 47127b8..2ba1dbf 100644
--- a/test/test_legacy_server.py
+++ b/test/test_legacy_server.py
@@ -5,7 +5,8 @@
 import pytest
 
 from grpc4bmi.bmi_grpc_legacy_server import BmiLegacyServer02
-from test.flatbmiheat import FlatLegacyBmiHeat
+from test.fake_models import Rect3DGridModel
+from test.legacybmiheat import LegacyBmiHeat
 
 """
 Unit tests for the BMI server class. Every test performs cross-checking with a local instance of the BMI heat toy model.
@@ -39,8 +40,8 @@ def make_list(obj):
     return [obj]
 
 
-def make_bmi_classes(init=False):
-    server, local = BmiLegacyServer02(FlatLegacyBmiHeat()), FlatLegacyBmiHeat()
+def make_bmi_classes(init=False, bmi_class=LegacyBmiHeat):
+    server, local = BmiLegacyServer02(bmi_class()), bmi_class()
     if init:
         req = RequestStub()
         numpy.random.seed(0)
@@ -158,6 +159,13 @@ def test_get_var_nbytes():
     assert server.getVarNBytes(request, None).nbytes == local.get_var_nbytes(varname)
     del server
 
+def test_get_var_itemsize():
+    server, local = make_bmi_classes(True)
+    request = RequestStub()
+    varname = local.get_output_var_names()[0]
+    setattr(request, "name", varname)
+    assert server.getVarItemSize(request, None).size == local.get_var_itemsize(varname)
+    del server
 
 def test_get_var_values():
     server, local = make_bmi_classes(True)
@@ -189,18 +197,6 @@ def test_get_vals_indices():
     numpy.testing.assert_allclose(server.getValueAtIndices(request, None).values_double.values, values.flatten())
 
 
-def test_get_vals_indices_2d():
-    server, local = make_bmi_classes(True)
-    request = RequestStub()
-    varname = local.get_output_var_names()[0]
-    indices = numpy.array([[0, 1], [1, 0], [2, 2]])
-    setattr(request, "name", varname)
-    setattr(request, "indices", indices.flatten())
-    setattr(request, "index_size", 2)
-    values = local.get_value_at_indices(varname, indices)
-    numpy.testing.assert_allclose(server.getValueAtIndices(request, None).values_double.values, values.flatten())
-
-
 def test_set_var_values():
     server, local = make_bmi_classes(True)
     request = RequestStub()
@@ -266,7 +262,9 @@ def test_get_grid_shape():
     varname = local.get_output_var_names()[0]
     grid_id = local.get_var_grid(varname)
     setattr(request, "grid_id", grid_id)
-    assert tuple(server.getGridShape(request, None).shape) == local.get_grid_shape(grid_id)
+    actual = tuple(server.getGridShape(request, None).shape)
+    expected = local.get_grid_shape(grid_id)
+    numpy.testing.assert_allclose(actual, expected)
 
 
 def test_get_grid_spacing():
@@ -275,7 +273,9 @@ def test_get_grid_spacing():
     varname = local.get_output_var_names()[0]
     grid_id = local.get_var_grid(varname)
     setattr(request, "grid_id", grid_id)
-    assert tuple(server.getGridSpacing(request, None).spacing) == local.get_grid_spacing(grid_id)
+    actual = tuple(server.getGridSpacing(request, None).spacing) 
+    expected = local.get_grid_spacing(grid_id)
+    numpy.testing.assert_allclose(actual, expected)
 
 
 def test_get_grid_origin():
@@ -284,4 +284,47 @@ def test_get_grid_origin():
     varname = local.get_output_var_names()[0]
     grid_id = local.get_var_grid(varname)
     setattr(request, "grid_id", grid_id)
-    assert tuple(server.getGridOrigin(request, None).origin) == local.get_grid_origin(grid_id)
+    actual = tuple(server.getGridOrigin(request, None).origin) 
+    expected = local.get_grid_origin(grid_id)
+    numpy.testing.assert_allclose(actual, expected)
+
+class LegacyRect3DGridModel(Rect3DGridModel):
+    def get_grid_x(self, grid: int):
+        return numpy.array([0.1, 0.2, 0.3, 0.4])
+
+    def get_grid_y(self, grid: int):
+        return numpy.array([1.1, 1.2, 1.3])
+
+    def get_grid_z(self, grid: int):
+        return numpy.array([2.1, 2.2])
+
+class TestLegacyRect3DGrid:
+    def test_get_grid_x(self):
+        server, local = make_bmi_classes(True, LegacyRect3DGridModel)
+        request = RequestStub()
+        varname = local.get_output_var_names()[0]
+        grid_id = local.get_var_grid(varname)
+        setattr(request, "grid_id", grid_id)
+        actual = tuple(server.getGridX(request, None).coordinates) 
+        expected = local.get_grid_x(grid_id)
+        numpy.testing.assert_allclose(actual, expected)
+
+    def test_get_grid_y(self):
+        server, local = make_bmi_classes(True, LegacyRect3DGridModel)
+        request = RequestStub()
+        varname = local.get_output_var_names()[0]
+        grid_id = local.get_var_grid(varname)
+        setattr(request, "grid_id", grid_id)
+        actual = tuple(server.getGridY(request, None).coordinates) 
+        expected = local.get_grid_y(grid_id)
+        numpy.testing.assert_allclose(actual, expected)
+
+    def test_get_grid_z(self):
+        server, local = make_bmi_classes(True, LegacyRect3DGridModel)
+        request = RequestStub()
+        varname = local.get_output_var_names()[0]
+        grid_id = local.get_var_grid(varname)
+        setattr(request, "grid_id", grid_id)
+        actual = tuple(server.getGridZ(request, None).coordinates) 
+        expected = local.get_grid_z(grid_id)
+        numpy.testing.assert_allclose(actual, expected)
diff --git a/test/test_memoized.py b/test/test_memoized.py
new file mode 100644
index 0000000..c0c831f
--- /dev/null
+++ b/test/test_memoized.py
@@ -0,0 +1,102 @@
+from unittest.mock import patch
+
+from heat import BmiHeat
+import numpy as np
+from numpy.testing import assert_allclose
+import pytest
+
+from grpc4bmi.bmi_memoized import MemoizedBmi
+from test.fake_models import Rect3DGridModel, UnstructuredGridBmiModel
+
+
+@pytest.mark.parametrize(
+    'model,mut_name,mut_args',
+    [
+        (BmiHeat(), 'get_component_name', tuple()),
+        (BmiHeat(), 'get_input_item_count', tuple()),
+        (BmiHeat(), 'get_output_item_count', tuple()),
+        (BmiHeat(), 'get_input_var_names', tuple()),
+        (BmiHeat(), 'get_output_var_names', tuple()),
+        (BmiHeat(), 'get_start_time', tuple()),
+        (BmiHeat(), 'get_end_time', tuple()),
+        (BmiHeat(), 'get_time_step', tuple()),
+        (BmiHeat(), 'get_time_units', tuple()),
+        (BmiHeat(), 'get_var_type', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_units', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_itemsize', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_nbytes', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_grid', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_location', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_grid_rank', [0]),
+        (BmiHeat(), 'get_grid_size', [0]),
+        (BmiHeat(), 'get_grid_type', [0]),
+        (BmiHeat(), 'get_grid_shape', [0, np.zeros((2,))]),
+        (BmiHeat(), 'get_grid_spacing', [0, np.zeros((2,))]),
+        (BmiHeat(), 'get_grid_origin', [0, np.zeros((2,))]),
+        (Rect3DGridModel(), 'get_grid_x', [0, np.zeros((4,))]),
+        (Rect3DGridModel(), 'get_grid_y', [0, np.zeros((3,))]),
+        (Rect3DGridModel(), 'get_grid_z', [0, np.zeros((2,))]),
+        (UnstructuredGridBmiModel(), 'get_grid_node_count', [0]),
+        (UnstructuredGridBmiModel(), 'get_grid_edge_count', [0]),
+        (UnstructuredGridBmiModel(), 'get_grid_face_count', [0]),
+        (UnstructuredGridBmiModel(), 'get_grid_edge_nodes', [0, np.zeros((16,))]),
+        (UnstructuredGridBmiModel(), 'get_grid_face_edges', [0, np.zeros((11,))]),
+        (UnstructuredGridBmiModel(), 'get_grid_face_nodes', [0, np.zeros((11,))]),
+        (UnstructuredGridBmiModel(), 'get_grid_nodes_per_face', [0, np.zeros((3,))]),
+    ]
+)
+def test_memoized_methods(model, mut_name, mut_args):
+    with patch.object(model, mut_name, wraps=getattr(model, mut_name)) as mock_method:
+        client = MemoizedBmi(model)
+        client.initialize(None)
+        mot = getattr(client, mut_name)
+
+        first_result = mot(*mut_args)
+        second_result = mot(*mut_args)
+
+        if isinstance(first_result, np.ndarray):
+            assert_allclose(first_result, second_result)
+        else:
+            assert first_result == second_result
+        assert mock_method.call_count == 1
+
+
+@pytest.mark.parametrize(
+    'mut_name,mut_args',
+    [
+        ('update', tuple()),
+        ('update_until', [2]),
+        ('finalize', tuple()),
+        ('get_current_time', tuple()),
+        ('get_value', ['plate_surface__temperature', np.zeros((200,))]),
+        ('get_value_ptr', ['plate_surface__temperature']),
+        ('get_value_at_indices', ['plate_surface__temperature', np.array([0, 0, 0]), [1, 2, 3]]),
+        ('set_value', ['plate_surface__temperature', np.ones((200,))]),
+        ('set_value_at_indices', ['plate_surface__temperature', [1, 2, 3], [4, 5, 6]]),
+    ]
+)
+def test_nonmemoized_methods(mut_name, mut_args):
+    model = BmiHeat()
+    with patch.object(model, mut_name, wraps=getattr(model, mut_name)) as mock_method:
+        client = MemoizedBmi(model)
+        client.initialize(None)
+        mot = getattr(client, mut_name)
+
+        mot(*mut_args)
+        mot(*mut_args)
+
+        assert mock_method.call_count == 2
+
+
+def test_initialize_clears_cache():
+    model = BmiHeat()
+    client = MemoizedBmi(model)
+    client.initialize(None)
+    # Fill cache
+    client.get_start_time()
+    # Clear cache
+    client.initialize(None)
+
+    with patch.object(model, 'get_start_time', wraps=model.get_start_time) as mock_method:
+        client.get_start_time()
+        assert mock_method.call_count == 1
diff --git a/test/test_optionaldest.py b/test/test_optionaldest.py
new file mode 100644
index 0000000..268a2e6
--- /dev/null
+++ b/test/test_optionaldest.py
@@ -0,0 +1,92 @@
+import numpy as np
+from heat import BmiHeat
+from numpy.testing import assert_allclose
+import pytest
+
+from grpc4bmi.bmi_optionaldest import OptionalDestBmi
+from test.fake_models import Rect3DGridModel, UnstructuredGridBmiModel
+
+
+@pytest.mark.parametrize(
+    'orig_model,method_name,method_args,expected_shape',
+    [
+        (BmiHeat(), 'get_value', ('plate_surface__temperature',), (200,)),
+        (BmiHeat(), 'get_value', ('plate_surface__temperature', np.zeros(200)), (200,)),
+        (BmiHeat(), 'get_grid_shape', (0,), (2,)),
+        (BmiHeat(), 'get_grid_shape', (0, np.zeros(2)), (2,)),
+        (BmiHeat(), 'get_grid_spacing', (0,), (2,)),
+        (BmiHeat(), 'get_grid_spacing', (0, np.zeros(2)), (2,)),
+        (BmiHeat(), 'get_grid_origin', (0,), (2,)),
+        (BmiHeat(), 'get_grid_origin', (0, np.zeros(2)), (2,)),
+        (BmiHeat(), 'get_value_at_indices', ['plate_surface__temperature', [1, 2, 3]], (3,)),
+        (Rect3DGridModel(), 'get_grid_x', [0], (4,)),
+        (Rect3DGridModel(), 'get_grid_x', [0, np.zeros((4,))], (4,)),
+        (Rect3DGridModel(), 'get_grid_y', [0], (3,)),
+        (Rect3DGridModel(), 'get_grid_y', [0, np.zeros((3,))], (3,)),
+        (Rect3DGridModel(), 'get_grid_z', [0], (2,)),        
+        (Rect3DGridModel(), 'get_grid_z', [0, np.zeros((2,))], (2,)),        
+        (UnstructuredGridBmiModel(), 'get_grid_edge_nodes', [0], (16,)),
+        (UnstructuredGridBmiModel(), 'get_grid_edge_nodes', [0, np.zeros((16,))], (16,)),
+        (UnstructuredGridBmiModel(), 'get_grid_face_edges', [0], (11,)),
+        (UnstructuredGridBmiModel(), 'get_grid_face_edges', [0, np.zeros((11,))], (11,)),
+        (UnstructuredGridBmiModel(), 'get_grid_face_nodes', [0], (11,)),
+        (UnstructuredGridBmiModel(), 'get_grid_face_nodes', [0, np.zeros((11,))], (11,)),
+        (UnstructuredGridBmiModel(), 'get_grid_nodes_per_face', [0], (3,)),        
+        (UnstructuredGridBmiModel(), 'get_grid_nodes_per_face', [0, np.zeros((3,))], (3,)),        
+    ]
+)
+def test_methods_with_optional_dest(orig_model, method_name, method_args, expected_shape):
+    model = OptionalDestBmi(orig_model)
+    model.initialize(None)
+
+    method = getattr(model, method_name)
+    result = method(*method_args)
+
+    assert result.shape == expected_shape
+
+
+@pytest.mark.parametrize(
+    'orig_model, method_name, method_args', 
+    [
+        (BmiHeat(), 'update', tuple()),
+        (BmiHeat(), 'update_until', [2]),
+        (BmiHeat(), 'finalize', tuple()),
+        (BmiHeat(), 'get_current_time', tuple()),
+        (BmiHeat(), 'get_component_name', tuple()),
+        (BmiHeat(), 'get_input_item_count', tuple()),
+        (BmiHeat(), 'get_output_item_count', tuple()),
+        (BmiHeat(), 'get_input_var_names', tuple()),
+        (BmiHeat(), 'get_output_var_names', tuple()),
+        (BmiHeat(), 'get_start_time', tuple()),
+        (BmiHeat(), 'get_end_time', tuple()),
+        (BmiHeat(), 'get_time_step', tuple()),
+        (BmiHeat(), 'get_time_units', tuple()),
+        (BmiHeat(), 'get_var_type', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_units', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_itemsize', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_nbytes', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_grid', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_var_location', ['plate_surface__temperature']),
+        (BmiHeat(), 'get_grid_rank', [0]),
+        (BmiHeat(), 'get_grid_size', [0]),
+        (BmiHeat(), 'get_grid_type', [0]),
+        (BmiHeat(), 'get_value_ptr', ['plate_surface__temperature']),
+        (BmiHeat(), 'set_value', ['plate_surface__temperature', np.ones((200,))]),
+        (BmiHeat(), 'set_value_at_indices', ['plate_surface__temperature', [1, 2, 3], [4, 5, 6]]),
+        (UnstructuredGridBmiModel(), 'get_grid_node_count', [0]),
+        (UnstructuredGridBmiModel(), 'get_grid_edge_count', [0]),
+        (UnstructuredGridBmiModel(), 'get_grid_face_count', [0]),
+    ]
+)
+def test_methods_with_no_dest(orig_model, method_name, method_args):
+    model = OptionalDestBmi(orig_model)
+    model.initialize(None)
+
+    method = getattr(model, method_name)
+    result = method(*method_args)
+
+    expected = getattr(orig_model, method_name)(*method_args)
+    if isinstance(result, np.ndarray):
+            assert_allclose(result, expected)
+    else:
+        assert result == expected
diff --git a/test/test_r.py b/test/test_r.py
new file mode 100644
index 0000000..bb08d75
--- /dev/null
+++ b/test/test_r.py
@@ -0,0 +1,72 @@
+import numpy as np
+import pytest
+
+from grpc4bmi.run_server import BmiR, build_r
+
+@pytest.fixture
+def model():
+    return build_r('FakeFailingRModel', 'test/fake.r')
+
+@pytest.mark.skipif(not BmiR, reason='R and its dependencies are not installed')
+class TestFakeFailingRModel:
+    @pytest.mark.parametrize(
+    'fn_name,fn_args',
+    [
+        ('get_component_name', tuple()),
+        ('get_input_item_count', tuple()),
+        ('get_output_item_count', tuple()),
+        ('get_input_var_names', tuple()),
+        ('get_output_var_names', tuple()),
+        ('get_start_time', tuple()),
+        ('get_end_time', tuple()),
+        ('get_time_step', tuple()),
+        ('get_time_units', tuple()),
+        ('get_var_type', ['plate_surface__temperature']),
+        ('get_var_units', ['plate_surface__temperature']),
+        ('get_var_itemsize', ['plate_surface__temperature']),
+        ('get_var_nbytes', ['plate_surface__temperature']),
+        ('get_var_grid', ['plate_surface__temperature']),
+        ('get_var_location', ['plate_surface__temperature']),
+        ('get_grid_shape', [0, np.zeros((1,))]),
+        ('get_grid_x', [0, np.zeros((1,))]),
+        ('get_grid_y', [0, np.zeros((1,))]),
+        ('get_grid_z', [0, np.zeros((1,))]),
+        ('get_grid_spacing', [0, np.zeros((1,))]),
+        ('get_grid_origin', [0, np.zeros((1,))]),
+        ('get_grid_rank', [0]),
+        ('get_grid_size', [0]),
+        ('get_grid_type', [0]),
+        ('initialize', ['config.ini']),
+        ('update', tuple()),
+        ('update_until', [2]),
+        ('finalize', tuple()),
+        ('get_current_time', tuple()),
+        ('get_value', ['plate_surface__temperature', np.zeros((1,))]),
+        ('get_value_at_indices', ['plate_surface__temperature', np.zeros((1,)), [1, 2, 3]]),
+        ('set_value', ['plate_surface__temperature', np.ones((10, 20))]),
+        ('set_value_at_indices', ['plate_surface__temperature', [1, 2, 3], [4, 5, 6]]),
+        ('get_grid_node_count', (0,)),
+        ('get_grid_edge_count', (0,)),
+        ('get_grid_face_count', (0,)),
+        ('get_grid_edge_nodes', (0, np.empty(0))),
+        ('get_grid_face_nodes', (0, np.empty(0))),
+        ('get_grid_face_edges', (0, np.empty(0))),
+        ('get_grid_nodes_per_face', (42, np.empty(0))),
+    ]
+)
+    def test_r_function_is_called(self, model: BmiR, fn_name, fn_args):
+        # Every method in 'test/fake.r' executes the stop error action
+        # So if no stop then no r function is called
+        from rpy2.rinterface_lib.embedded import RRuntimeError
+        with pytest.raises(RRuntimeError, match="Always fails"):
+            fn = getattr(model, fn_name)
+            fn(*fn_args)
+
+    @pytest.mark.parametrize('fn_name,fn_args', [
+        ('get_value_ptr', ['plate_surface__temperature']),
+    ])
+    def test_notimplemented(self, model: BmiR, fn_name, fn_args):
+        with pytest.raises(NotImplementedError):
+            fn = getattr(model, fn_name)
+            fn(*fn_args)
+        
\ No newline at end of file
diff --git a/test/test_server.py b/test/test_server.py
index 24b71ab..c187969 100644
--- a/test/test_server.py
+++ b/test/test_server.py
@@ -6,12 +6,12 @@
 import pytest
 import grpc
 from google.rpc import error_details_pb2, status_pb2
+from heat import BmiHeat
 
 from grpc4bmi import bmi_pb2
 from grpc4bmi.bmi_grpc_server import BmiServer
 from grpc4bmi.reserve import reserve_values, reserve_grid_shape, reserve_grid_padding
 from test.fake_models import SomeException, FailingModel, Rect3DGridModel, UnstructuredGridBmiModel
-from test.flatbmiheat import FlatBmiHeat
 
 """
 Unit tests for the BMI server class. Every test performs cross-checking with a local instance of the BMI heat toy model.
@@ -46,7 +46,7 @@ def make_list(obj):
 
 
 def make_bmi_classes(init=False):
-    server, local = BmiServer(FlatBmiHeat()), FlatBmiHeat()
+    server, local = BmiServer(BmiHeat()), BmiHeat()
     if init:
         req = RequestStub()
         numpy.random.seed(0)
@@ -219,24 +219,11 @@ def test_get_value_at_indices():
     varname = local.get_output_var_names()[0]
     indices = numpy.array([29, 8, 19, 81])
     setattr(request, "name", varname)
-    setattr(request, "indices", indices.flatten())
+    setattr(request, "indices", indices)
     setattr(request, "index_size", 1)
     dest = numpy.empty(4)
     values = local.get_value_at_indices(varname, dest, indices)
-    numpy.testing.assert_allclose(server.getValueAtIndices(request, None).values_double.values, values.flatten())
-
-
-def test_get_vals_indices_2d():
-    server, local = make_bmi_classes(True)
-    request = RequestStub()
-    varname = local.get_output_var_names()[0]
-    indices = numpy.array([[0, 1], [1, 0], [2, 2]])
-    setattr(request, "name", varname)
-    setattr(request, "indices", indices.flatten())
-    setattr(request, "index_size", 2)
-    dest = numpy.empty(6)
-    values = local.get_value_at_indices(varname, dest, indices)
-    numpy.testing.assert_allclose(server.getValueAtIndices(request, None).values_double.values, values.flatten())
+    numpy.testing.assert_allclose(server.getValueAtIndices(request, None).values_double.values, values)
 
 
 def test_set_var_values():
@@ -263,7 +250,7 @@ def test_set_values_indices():
     values = numpy.array([0.123, 4.567, 8.901])
     setattr(request, "name", varname)
     setattr(request, "values_double", value_wrapper(values))
-    setattr(request, "indices", indices.flatten())
+    setattr(request, "indices", indices)
     setattr(request, "index_size", 1)
     server.setValueAtIndices(request, None)
     delattr(request, "values_double")
@@ -372,7 +359,7 @@ def test_method_exceptions_with_stacktrace(server_method, server_request):
     metadata = status_pb2.Status.FromString(status.trailing_metadata[0][1])
     debuginfo = error_details_pb2.DebugInfo()
     metadata.details[0].Unpack(debuginfo)
-    assert debuginfo.detail == "SomeException('Bmi method always fails',)"
+    assert debuginfo.detail == "SomeException('Bmi method always fails')"
     assert len(debuginfo.stack_entries) > 0
 
 
diff --git a/test/test_singularity.py b/test/test_singularity.py
index 0458e07..1f9d8b1 100644
--- a/test/test_singularity.py
+++ b/test/test_singularity.py
@@ -1,33 +1,93 @@
+import os
+import subprocess
+from tempfile import TemporaryDirectory
 from textwrap import dedent
+from typing import Type, Union
 
 from os import environ
 import pytest
+from grpc import RpcError
 from nbconvert.preprocessors import ExecutePreprocessor
 from nbformat.v4 import new_notebook, new_code_cell
+import numpy as np
+from typeguard import TypeCheckError
 
-from grpc4bmi.bmi_client_singularity import BmiClientSingularity
-from grpc4bmi.reserve import reserve_grid_padding, reserve_values
+from grpc4bmi.bmi_client_singularity import SUPPORTED_APPTAINER_VERSIONS, SUPPORTED_SINGULARITY_VERSIONS, BmiClientSingularity, check_singularity_version_string
+from grpc4bmi.exceptions import ApptainerVersionException, DeadContainerException, SingularityVersionException
+from grpc4bmi.reserve import reserve_grid_padding
+from test.conftest import write_config, write_datafile
 
 IMAGE_NAME = "docker://ewatercycle/walrus-grpc4bmi:v0.3.1"
 
 
 @pytest.fixture()
 def walrus_model(tmp_path, walrus_input):
-    model = BmiClientSingularity(image=IMAGE_NAME, input_dir=str(tmp_path))
+    model = BmiClientSingularity(image=IMAGE_NAME, work_dir=str(tmp_path))
     yield model
     del model
 
 
 @pytest.fixture()
-def walrus_model_with_extra_volume(walrus_input_on_extra_volume):
-    (input_dir, docker_extra_volumes) = walrus_input_on_extra_volume
-    extra_volumes = {str(k): str(v['bind']) for k, v in docker_extra_volumes.items()}
-    model = BmiClientSingularity(image=IMAGE_NAME, input_dir=str(input_dir), extra_volumes=extra_volumes)
+def walrus_model_with_input_dir(tmp_path, walrus_input):
+    work_dir = TemporaryDirectory()
+    model = BmiClientSingularity(image=IMAGE_NAME, work_dir=work_dir.name, input_dirs=[str(tmp_path)])
     yield model
     del model
+    work_dir.cleanup()
 
 
-class TestBmiClientDocker:
+@pytest.fixture()
+def walrus_model_with_2input_dirs(walrus_2input_dirs, tmp_path):
+    work_dir = tmp_path / 'work'
+    work_dir.mkdir()
+    input_dirs = walrus_2input_dirs['input_dirs']
+    model = BmiClientSingularity(image=IMAGE_NAME, work_dir=str(work_dir), input_dirs=input_dirs)
+    yield model
+    del model
+
+
+@pytest.fixture()
+def walrus_model_with_work_dir(tmp_path):
+    work_dir = tmp_path
+    write_config(work_dir / 'config.yml', 'PEQ_Hupsel.dat')
+    write_datafile(work_dir / 'PEQ_Hupsel.dat')
+
+    model = BmiClientSingularity(image=IMAGE_NAME, work_dir=str(work_dir))
+    yield model, work_dir
+    del model
+
+
+@pytest.fixture()
+def walrus_model_with_config_inside_image(tmp_path):
+    input_dir = tmp_path / 'input'
+    input_dir.mkdir()
+    data_file = input_dir / 'PEQ_Hupsel.dat'
+    write_datafile(data_file)
+    cfg_file = input_dir / 'config.yml'
+    write_config(cfg_file, '/scratch/PEQ_Hupsel.dat')
+    def_file = tmp_path / 'walrus.def'
+    def_file.write_text(f'''Bootstrap: docker
+From: {IMAGE_NAME.replace('docker://', '')}
+
+%files
+  {data_file} /scratch/PEQ_Hupsel.dat'
+  {cfg_file} /scratch/config.yml'
+''')
+    image = tmp_path / 'walrus-image'
+    subprocess.run(['singularity', 'build', '--sandbox', '--fakeroot', image, def_file])
+
+    work_dir = tmp_path / 'work'
+    work_dir.mkdir()
+    model = BmiClientSingularity(str(image), str(work_dir))
+
+    yield model
+
+    del model
+    # Singularity generates some files with permissions that pytest clean up does not handle
+    subprocess.run(['rm', '-rf', str(image)])
+
+
+class TestBmiClientSingularity:
     def test_component_name(self, walrus_model):
         assert walrus_model.get_component_name() == 'WALRUS'
 
@@ -35,7 +95,11 @@ def test_initialize(self, walrus_input, walrus_model):
         walrus_model.initialize(str(walrus_input))
         assert walrus_model.get_current_time() == walrus_model.get_start_time()
 
-    def test_get_value_ptr(self, walrus_model):
+    def test_initialize_absent_configfile(self, walrus_model):
+        with pytest.raises(RpcError, match='Error in private'):
+            walrus_model.initialize('configfilethatdoesnotexist')
+
+    def test_get_value_ref(self, walrus_model):
         with pytest.raises(NotImplementedError):
             walrus_model.get_value_ptr('Q')
 
@@ -44,24 +108,115 @@ def test_get_grid_origin(self, walrus_input, walrus_model):
         grid_id = walrus_model.get_var_grid('Q')
         assert len(walrus_model.get_grid_origin(grid_id, reserve_grid_padding(walrus_model, grid_id))) == 2
 
-    def test_extra_volumes(self, walrus_model_with_extra_volume):
-        walrus_model_with_extra_volume.initialize('/data/input/config.yml')
-        walrus_model_with_extra_volume.update()
+    def test_input_dir(self, walrus_input, walrus_model_with_input_dir):
+        walrus_model_with_input_dir.initialize(str(walrus_input))
+        walrus_model_with_input_dir.update()
+
+        # After initialization and update the forcings have been read from the forcing dir
+        assert len(walrus_model_with_input_dir.get_value('Q', np.zeros(1,))) == 1
+
+    def test_2input_dirs(self, walrus_2input_dirs, walrus_model_with_2input_dirs):
+        config_file = walrus_2input_dirs['cfg']
+        walrus_model_with_2input_dirs.initialize(config_file)
+        walrus_model_with_2input_dirs.update()
 
-        # After initialization and update the forcings have been read from the extra volume
-        result = reserve_values(walrus_model_with_extra_volume, 'Q')
-        assert len(walrus_model_with_extra_volume.get_value('Q', result)) == 1
+        # After initialization and update the forcings have been read from the forcing dir
+        assert len(walrus_model_with_2input_dirs.get_value('Q', np.zeros(1,))) == 1
+
+    def test_workdir_absolute(self, walrus_model_with_work_dir):
+        model, work_dir = walrus_model_with_work_dir
+        model.initialize(str(work_dir / 'config.yml'))
+        model.update()
+
+        # After initialization and update the forcings have been read from the work dir
+        assert len(model.get_value('Q', np.zeros(1,))) == 1
+
+    def test_workdir_relative(self, walrus_model_with_work_dir):
+        model, _work_dir = walrus_model_with_work_dir
+        model.initialize('config.yml')
+        model.update()
+
+        # After initialization and update the forcings have been read from the work dir
+        assert len(model.get_value('Q', np.zeros(1,))) == 1
+
+    def test_inputdir_absent(self, tmp_path):
+        dirthatdoesnotexist = 'dirthatdoesnotexist'
+        input_dir = tmp_path / dirthatdoesnotexist
+        work_dir = tmp_path / 'work'
+        work_dir.mkdir()
+        with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist):
+            BmiClientSingularity(image=IMAGE_NAME, work_dir=str(work_dir), input_dirs=[str(input_dir)])
+
+    def test_workdir_absent(self, tmp_path):
+        dirthatdoesnotexist = 'dirthatdoesnotexist'
+        work_dir = tmp_path / dirthatdoesnotexist
+        with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist):
+            BmiClientSingularity(image=IMAGE_NAME, work_dir=str(work_dir))
+
+    def test_same_inputdir_and_workdir(self, tmp_path):
+        some_dir = str(tmp_path)
+        match = 'Found work_dir equal to one of the input directories. Please drop that input dir.'
+        with pytest.raises(ValueError, match=match):
+            BmiClientSingularity(image=IMAGE_NAME, input_dirs=(some_dir,), work_dir=some_dir)
+
+    def test_with_config_inside_image(self, walrus_model_with_config_inside_image):
+        model = walrus_model_with_config_inside_image
+        model.initialize('/scratch/config.yml')
+        model.update()
+
+        # After initialization and update the forcings have been read from the scratch dir
+        assert len(model.get_value('Q', np.zeros(1,))) == 1
+
+    def test_workdir_as_number(self):
+        with pytest.raises(TypeCheckError, match='is not an instance of str'):
+            BmiClientSingularity(image=IMAGE_NAME, work_dir=42)
+
+    def test_inputdirs_as_str(self, tmp_path):
+        some_dir = str(tmp_path)
+        with pytest.raises(TypeError, match='must be collections.abc.Iterable'):
+            BmiClientSingularity(image=IMAGE_NAME, input_dirs='old type', work_dir=some_dir)
+
+    def test_inputdirs_as_number(self, tmp_path):
+        some_dir = str(tmp_path)
+        with pytest.raises(TypeCheckError, match='is not an instance of collections.abc.Iterable'):
+            BmiClientSingularity(image=IMAGE_NAME, input_dirs=42, work_dir=some_dir)
+
+
+class TestRedirectOutput:
+    EXPECTED = 'Hello from Docker!'
+
+    @pytest.fixture
+    def image(self):
+        hello_image = 'docker://hello-world'
+        # Cache image, first test does not use delay time to build image
+        os.system(f'singularity run {hello_image}')
+        return hello_image
+
+    def test_default(self, image, tmp_path, capfd):
+        with pytest.raises(DeadContainerException) as excinf:
+            BmiClientSingularity(image=image, work_dir=str(tmp_path), delay=2)
+
+        assert self.EXPECTED not in capfd.readouterr().out
+        assert self.EXPECTED in excinf.value.logs
+
+    def test_devnull(self, image, tmp_path, capfd):
+        with pytest.raises(DeadContainerException) as excinf:
+            BmiClientSingularity(image=image, work_dir=str(tmp_path), capture_logs=False, delay=2)
+
+        assert self.EXPECTED not in capfd.readouterr().out
+        assert self.EXPECTED not in excinf.value.logs
 
 
 @pytest.fixture
-def notebook():
+def notebook(tmp_path):
+    tmp_path.mkdir(exist_ok=True)
     cells = [
         new_code_cell(dedent("""\
             from grpc4bmi.bmi_client_singularity import BmiClientSingularity
-            walrus_model = BmiClientSingularity(image='{0}')
+            walrus_model = BmiClientSingularity(image='{0}', work_dir='{1}')
             assert walrus_model.get_component_name() == 'WALRUS'
             del walrus_model
-        """.format(IMAGE_NAME)))
+        """.format(IMAGE_NAME, str(tmp_path))))
     ]
     return new_notebook(cells=cells)
 
@@ -70,3 +225,27 @@ def notebook():
 def test_from_notebook(notebook, tmp_path):
     ep = ExecutePreprocessor(timeout=600, kernel_name='python3')
     ep.preprocess(notebook, {'metadata': {'path': tmp_path}})
+
+class Test_check_singularity_version_string:
+    @pytest.mark.parametrize("test_input", [
+        ('singularity version 3.6.0'),
+        ('singularity version 3.8.7'),  # Last OSS version before fork
+        ('apptainer version 1.0.0-rc.2'),  
+        ('apptainer version 1.0.0'),
+        ('apptainer version 1.0.3'),
+        ('apptainer version 1.1.0-rc.3'),
+        ('apptainer version 1.1.2'),
+    ])
+    def test_ok(self, test_input: str):
+        result = check_singularity_version_string(test_input)
+        assert result
+
+
+    @pytest.mark.parametrize("test_input,error_class,expected", [
+        ('singularity version 3.5.0', SingularityVersionException, SUPPORTED_SINGULARITY_VERSIONS),
+        ('apptainer version 1.0.0-rc.1', ApptainerVersionException, SUPPORTED_APPTAINER_VERSIONS),
+        ('apptainer version 0.1.0', ApptainerVersionException, SUPPORTED_APPTAINER_VERSIONS),
+    ])
+    def test_too_old(self, test_input: str, error_class: Union[Type[ApptainerVersionException], Type[SingularityVersionException]], expected: str):
+        with pytest.raises(error_class, match=expected):
+            check_singularity_version_string(test_input)
diff --git a/test/test_subproc.py b/test/test_subproc.py
index b57225b..7a0e20f 100644
--- a/test/test_subproc.py
+++ b/test/test_subproc.py
@@ -7,7 +7,7 @@
 
 from grpc4bmi.reserve import reserve_values_at_indices, reserve_values, reserve_grid_shape, reserve_grid_padding
 from test.fake_models import HugeModel
-from test.flatbmiheat import FlatBmiHeat
+from test.legacybmiheat import BmiHeat
 
 from grpc4bmi.bmi_client_subproc import BmiClientSubProcess
 
@@ -22,8 +22,8 @@
 def make_bmi_classes(init=False):
     numpy.random.seed(0)
     os.environ["PYTHONPATH"] = os.path.dirname(os.path.abspath(__file__))
-    client = BmiClientSubProcess("flatbmiheat.FlatBmiHeat")
-    local = FlatBmiHeat()
+    client = BmiClientSubProcess("heat.BmiHeat")
+    local = BmiHeat()
     if init:
         client.initialize(None)
         local.initialize(None)
@@ -156,6 +156,18 @@ def test_get_value_huge():
     del client
 
 
+def test_get_value_huge():
+    os.environ["PYTHONPATH"] = os.path.dirname(os.path.abspath(__file__))
+    client = BmiClientSubProcess("fake_models.HugeModel")
+    local = HugeModel()
+    varname = local.get_output_var_names()[0]
+
+    result = client.get_value(varname, reserve_values(client, varname))
+    expected = local.get_value(varname, reserve_values(local, varname))
+    assert numpy.array_equal(result, expected)
+    del client
+
+
 def test_get_var_ptr():
     client, local = make_bmi_classes(True)
     varname = local.get_output_var_names()[0]
diff --git a/test/test_utils.py b/test/test_utils.py
deleted file mode 100644
index 6053c29..0000000
--- a/test/test_utils.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import pytest
-
-from grpc4bmi.utils import stage_config_file
-
-
-class TestStageConfigFile:
-    def test_inside_inputdir(self, tmpdir):
-        input_dir = tmpdir.mkdir('input')
-        c = input_dir.join('config.cfg')
-        c.write("something")
-
-        result = stage_config_file(c, input_dir, input_mount_point='/data/input')
-        assert result == '/data/input/config.cfg'
-
-    def test_outside_inputdir(self, tmpdir):
-        input_dir = tmpdir.mkdir('input')
-        c = tmpdir.join('config.cfg')
-        c.write("something")
-
-        result = stage_config_file(c, input_dir, input_mount_point='/data/input')
-        assert result == '/data/input/config.cfg'
-
-    def test_outside_inputdir_beforeconfig(self, tmpdir):
-        input_dir = tmpdir.mkdir('ainput')
-        c = tmpdir.join('config.cfg')
-        c.write("something")
-
-        result = stage_config_file(c, input_dir, input_mount_point='/data/input')
-        assert result == '/data/input/config.cfg'
-
-    def test_cfg_in_container(self, tmpdir):
-        input_dir = tmpdir.mkdir('input')
-
-        c = '/somewhere/config.cfg'
-        result = stage_config_file(c, input_dir, input_mount_point='/data/input')
-        assert result == c
-
-    def test_noinputdir(self, tmpdir):
-        c = tmpdir.join('config.cfg')
-        c.write("something")
-
-        with pytest.raises(Exception) as exc_info:
-            stage_config_file(c, None, None)
-        assert str(exc_info.value).startswith('Unable to copy')