Repository: TREX-CoE/trexio (language: python)

Issue:
add python interface



Discussion / hints:
simple prototype is done using SWIG wrapper that acts on TREXIO header files
SWIG can be also used to generate interfaces with other programming languages, for example OCaml
TODO: find a way to use basic Python lists or numpy arrays directly (for now intermediate transformation based on <type>Array class is needed)

Perhaps it is easier and more user-friendly to implement the Python interface from scratch instead of relying on SWIG. The advantage of this approach is that the interface can be written in pythonic object-oriented manner with direct access to all desirable features (e.g. lists, dictionaries, numpy arrays). In this case, `h5py` package has to be used in the HDF5 back end. 
Do what you think is best, as long as you can auto-generate it ;-)

Fix patch:
diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml
index a6ddbf20..3d69bb16 100644
--- a/.github/workflows/actions.yml
+++ b/.github/workflows/actions.yml
@@ -30,7 +30,19 @@ jobs:
         
       - name: check TREXIO
         run: make check
-        
+
+      - name: create virtual environment
+        run: | 
+          python3 -m venv --clear pytrexio-venv
+          source pytrexio-venv/bin/activate
+      
+      - name: install Python API
+        run: make python-install
+        # alternatively we can also run pip install trexio to check PyPI installation
+
+      - name: check Python API
+        run: make python-test
+
       - name: clean
         run: make clean
 
diff --git a/.gitignore b/.gitignore
index 859e3f84..8b733a20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,11 @@
 */*.la
 */*.lo
 aclocal.m4
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
 autom4te.cache/
 compile
 config.guess
@@ -16,6 +21,7 @@ config.h.in
 config.h
 depcomp
 include/config.h.in
+include/config.h.in~
 include/stamp-h1
 install-sh
 libtool
diff --git a/.gitmodules b/.gitmodules
index 4f782552..1e764cd7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "docs/org-html-themes"]
 	path = docs/org-html-themes
 	url = https://github.com:fniessen/org-html-themes.git
+[submodule "python/examples"]
+	path = python/examples
+	url = git@github.com:TREX-CoE/trexio-tutorials.git
diff --git a/Makefile.am b/Makefile.am
index ed7067d4..8ae04130 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -87,6 +87,8 @@ TESTS_C = \
   tests/io_dset_float_text \
   tests/io_dset_int_hdf5 \
   tests/io_dset_int_text \
+  tests/io_safe_dset_float_hdf5 \
+  tests/io_safe_dset_float_text \
   tests/io_str_hdf5 \
   tests/io_str_text \
   tests/io_dset_str_hdf5 \
@@ -114,6 +116,8 @@ tests_io_dset_float_hdf5_LDFLAGS = -no-install
 tests_io_dset_float_text_LDFLAGS = -no-install
 tests_io_dset_int_hdf5_LDFLAGS = -no-install
 tests_io_dset_int_text_LDFLAGS = -no-install
+tests_io_safe_dset_float_hdf5_LDFLAGS = -no-install
+tests_io_safe_dset_float_text_LDFLAGS = -no-install
 tests_io_str_hdf5_LDFLAGS = -no-install
 tests_io_str_text_LDFLAGS = -no-install
 tests_io_dset_str_hdf5_LDFLAGS = -no-install
@@ -133,7 +137,7 @@ tests_test_f_SOURCES = $(test_trexio_f) tests/test_f.f90
 tests_test_f_LDFLAGS = -no-install
 
 clean-local:
-	-rm -rf -- *.dir/ *.h5
+	-rm -rf -- *.dir/ *.h5 __pycache__/
 
 # =============== DOCUMENTATION =============== #
 
@@ -153,6 +157,11 @@ $(HTML_FILES): docs/index.html
 
 # =============== DEVELOPER MODE =============== #
 
+SWIG          = @SWIG@ 
+HDF5_LDFLAGS  = @HDF5_LDFLAGS@ 
+HDF5_CFLAGS   = @HDF5_CFLAGS@ 
+HDF5_CPPFLAGS = @HDF5_CPPFLAGS@ 
+
 if TREXIO_DEVEL
 
 CLEANFILES += $(SOURCES) $(trexio_f) $(trexio_h)
@@ -179,7 +188,52 @@ cppcheck.out: $(trexio_h)
 	  --language=c --std=c99 -rp --platform=unix64  \
 	  -I../include *.c *.h 2>../$@
 
-.PHONY: cppcheck
+setup_py      = $(srcdir)/python/setup.py
+setup_cfg     = $(srcdir)/python/setup.cfg
+pytrexio_py   = $(srcdir)/python/pytrexio/pytrexio.py
+trexio_py     = $(srcdir)/python/trexio.py
+TEST_PY       = python/test/test_api.py
+pytrexio_c    = $(srcdir)/src/pytrexio_wrap.c
+pytrexio_i    = $(srcdir)/src/pytrexio.i
+numpy_i       = $(srcdir)/src/numpy.i
+
+
+python-test: $(TEST_PY)
+	python3 $(TEST_PY)
+	$(RM) -r -- __pycache__
+
+python-install: $(pytrexio_py) $(setup_py) $(setup_cfg)
+	cd python && \
+		./install_pytrexio.sh $(HDF5_CFLAGS) $(HDF5_CPPFLAGS) $(HDF5_LDFLAGS)
+
+python-sdist: $(pytrexio_py) $(setup_py) $(setup_cfg)
+	cd python && \
+		python setup.py sdist
+
+$(pytrexio_py): $(pytrexio_c) 
+	cd tools && ./prepare_python.sh
+
+# Build Python module and C wrapper code for TREXIO using SWIG
+# [?] swig -python -threads pytrexio.i ----> Add thread support for all the interface
+$(pytrexio_c): $(ORG_FILES) $(trexio_h) $(pytrexio_i) $(numpy_i)
+	cp $(trexio_h) src/
+	cd src/ && \
+		$(SWIG) -python -py3 -o pytrexio_wrap.c pytrexio.i
+	$(RM) -- src/trexio.h
+
+$(numpy_i):
+	wget https://raw.githubusercontent.com/numpy/numpy/main/tools/swig/numpy.i -O $(numpy_i) 
+
+check-numpy:
+	cd tools && ./check_numpy_i.sh
+
+CLEANFILES += $(pytrexio_c) \
+              $(pytrexio_py) \
+              $(trexio_py) \
+              python/src/*.c \
+              python/src/*.h
+
+.PHONY: cppcheck python-test python-install python-sdist check-numpy
 
 endif
 
diff --git a/README.md b/README.md
index 9980e04c..9171859d 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,12 @@
 [![build](https://github.com/TREX-CoE/trexio/actions/workflows/actions.yml/badge.svg)](https://github.com/TREX-CoE/trexio/actions/workflows/actions.yml)
 ![GitHub release (latest by date)](https://img.shields.io/github/v/release/TREX-CoE/trexio)
 
-TREX library fo efficient I/O.
+TREX library for efficient I/O.
 
 
 ## Minimal requirements (for users):
 
-- Autotools             (autoconf, automake, libtool)
+- Autotools             (autoconf >= 2.69, automake >= 1.11, libtool >= 2.2)
 - C compiler            (gcc/icc/clang)
 - Fortran compiler      (gfortran/ifort)
 - HDF5 library          (>= 1.8)
@@ -33,7 +33,7 @@ TREX library fo efficient I/O.
 
 - python3       (>= 3.6)
 - Emacs         (>= 26.0)
-
+- SWIG          (>= 4.0)
 
 ## Installation procedure from the GitHub repo clone (for developers):
 
@@ -56,7 +56,8 @@ The primary TREXIO API is composed of the following functions:
 - `trexio_has_[group]_[variable]`
 - `trexio_close`
 
-where `[group]` and `[variable]` substitutions correspond to the contents of the `trex.json` configuration file (for more details, see the corresponding [documentation](https://trex-coe.github.io/trexio/trex.html) page).
+where `[group]` and `[variable]` substitutions correspond to the contents of the `trex.json` configuration file 
+(for more details, see the corresponding [documentation](https://trex-coe.github.io/trexio/trex.html) page).
 For example, consider the `coord` variable (array), which belongs to the `nucleus` group. The TREXIO user can write or read it using `trexio_write_nucleus_coord` or `trexio_read_nucleus_coord` functions, respectively.
 
 Note: the `[variable]` names have to be unique only within the corresponding parent `[group]`. 
@@ -64,9 +65,20 @@ There is no naming conflict when, for example, `num` variable exists both in the
 These quantities can be accessed using the corresponding `trexio_[has|read|write]_nucleus_num` and `trexio_[has|read|write]_mo_num`, respectively.
 
 
+## Python API
+
+For more details regarding the installation and usage of the TREXIO Python API, 
+see [this page](python/README.md).
+
+
 ## Tutorial
 
-**TODO**
+TREXIO tutorials in Jupyter notebook format can be found in the 
+[corresponding GitHub repository](https://github.com/TREX-CoE/trexio-tutorials)
+or on [Binder](https://mybinder.org/v2/gh/TREX-CoE/trexio-tutorials/HEAD).
+
+For example, the tutorial covering TREXIO basics using benzene molecule as an example can be viewed and executed online by clicking on this badge: 
+[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/TREX-CoE/trexio-tutorials/HEAD?filepath=notebooks%2Ftutorial_benzene.ipynb)
 
 
 ## Technical documentation
@@ -76,8 +88,9 @@ These quantities can be accessed using the corresponding `trexio_[has|read|write
 
 ### Miscellaneous
 
-Note: The code should be compliant with the C99 [CERT C coding
-standard](https://resources.sei.cmu.edu/downloads/secure-coding/assets/sei-cert-c-coding-standard-2016-v01.pdf). This can be checked with the `cppcheck` tool.
+Note: The code should be compliant with the C99 
+[CERT C coding standard](https://resources.sei.cmu.edu/downloads/secure-coding/assets/sei-cert-c-coding-standard-2016-v01.pdf). 
+This can be checked with the `cppcheck` tool.
 
 
 
diff --git a/configure.ac b/configure.ac
index 5c1233a3..2c875487 100644
--- a/configure.ac
+++ b/configure.ac
@@ -50,6 +50,7 @@ AC_FC_LIBRARY_LDFLAGS
 # pkg-config
 PKG_PROG_PKG_CONFIG()
 PKG_CFLAGS=""
+PKG_LIBS=""
 
 AC_PROG_INSTALL
 AC_PROG_LIBTOOL
@@ -94,18 +95,29 @@ CPPFLAGS="${HDF5_CPPFLAGS} ${CPPFLAGS}"
 LDFLAGS="${HDF5_LDFLAGS} ${LDFLAGS}"
 LIBS="${HDF5_LIBS} ${LIBS}"
 
-# Check if HDF5 if available with pkg-config
+# Check if HDF5 is available with pkg-config.
 
 PKG_CHECK_MODULES([HDF5], [hdf5 >= 1.8], [
     PKG_HDF5="hdf5"
     ],[
     PKG_HDF5=""
    ])
-    PKG_CFLAGS="${PKG_CFLAGS}"
+PKG_CFLAGS="${HDF5_CFLAGS}"
+PKG_LIBS="${HDF5_LIBS}"
 
 AC_SUBST([PKG_HDF5])
 AC_SUBST([PKG_CFLAGS])
+AC_SUBST([PKG_LIBS])
 
+# The block below should only execute if the ax_lib_hdf5.m4 macro failed to find HDF5.
+# It is only needed to manually build Python API because setup.py depends on HDF5.
+
+if test "$HDF5_LDFLAGS" = "" || "$HDF5_CFLAGS" = ""; then
+  HDF5_LDFLAGS="${PKG_LIBS}"
+  HDF5_CFLAGS="${PKG_CFLAGS}"
+  AC_SUBST([HDF5_CFLAGS])
+  AC_SUBST([HDF5_LDFLAGS])
+fi
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_TYPE_INT32_T
@@ -126,6 +138,10 @@ if test "x${TREXIO_DEVEL}" != "x"; then
   AC_PROG_AWK
   AM_PATH_PYTHON([3.0])
 
+  AX_PKG_SWIG(4.0.0, [], AC_MSG_WARN([SWIG is required to build Python API.]) )
+  # The macro below performs Python benchmarks, but overlapping with AM_PATH_PYTHON
+  #AX_SWIG_PYTHON
+
   AC_CHECK_PROGS([EMACS],[emacs26 emacs],[no])
   if test x${EMACS} == xno ; then
     AC_MSG_ERROR([
diff --git a/docker/.gitignore b/docker/.gitignore
new file mode 100644
index 00000000..4aac0240
--- /dev/null
+++ b/docker/.gitignore
@@ -0,0 +1,2 @@
+tmp*
+trexio-*
diff --git a/docker/README b/docker/README
new file mode 100644
index 00000000..31a63003
--- /dev/null
+++ b/docker/README
@@ -0,0 +1,45 @@
+First, make sure to place the source code distribution (suffixed with .tar.gz) of the TREXIO Python API in this directory.
+
+TODO: the scripts have to be adapted for an arbitrary version of TREXIO and Python !
+
+Outside Docker image:
+
+# Build containers with hdf5 inside:
+
+# for manylinux2014_x86_64
+sudo docker build -t hdf5_1_12_on_2014_x86_64 . -f Dockerfile_2014_x86_64
+
+# for manylinux_2_24_x86_64
+sudo docker build -t hdf5_1_12_on_2_24_x86_64 . -f Dockerfile_2_24_x86_64
+
+# (create an image using HDF5 containers, see https://github.com/h5py/hdf5-manylinux)
+# -t hdf5_1_12_on_${PLATFORM} builds an image with a custom name (hdf5_1_12_1)
+
+# Run one of the produced containers in the interactive mode
+
+# for manylinux2014_x86_64
+sudo docker run --rm -it -e PLAT=manylinux2014_x86_64 -v `pwd`:/tmp hdf5_1_12_on_2014_x86_64 /bin/bash
+
+# for manylinux_2_24_x86_64
+sudo docker run --rm -it -e PLAT=manylinux_2_24_x86_64 -v `pwd`:/tmp hdf5_1_12_on_2_24_x86_64 /bin/bash
+
+#(PLAT specifies the base platform tag for manilinux)
+#-i (run docker container interactively)
+#-t f8e4232fa208 (run an image using ID if the image is not named)
+#/bin/bash (a binary to run inside a container environment)
+
+In the Docker image:
+
+cd tmp && ./build_manylinux_wheels_py_36_37_38.sh
+# (creates virtual environments, installs things with pip, produces conventional wheels and repairs them with auditwheel)
+
+# auditwheel repair trexio-0.1.0/dist/trexio-0.1.0-cp37-cp37m-linux_x86_64.whl
+# (repairs wheel and produces manylinux one)
+
+After Docker container execution:
+
+# the produced manylinux wheels are in trexio-0.1.0/wheelhouse directory
+
+# ALTERNATIVELY: one can copy the produced manylinux wheel from the container back to the host system
+sudo docker cp $CONTAINER_ID:/tmp/trexio-0.1.0/wheelhouse/MANYLINUX_NAME.whl directory-on-the-user-machine/
+
diff --git a/docker/build_manylinux_wheels_py_36_37_38_39.sh b/docker/build_manylinux_wheels_py_36_37_38_39.sh
new file mode 100755
index 00000000..9c6390b4
--- /dev/null
+++ b/docker/build_manylinux_wheels_py_36_37_38_39.sh
@@ -0,0 +1,188 @@
+#!/bin/bash
+
+set -x
+set -e 
+
+export H5_LDFLAGS=-L/usr/local/lib
+export H5_CFLAGS=-I/usr/local/include
+
+# install emacs for Debian
+
+#apt-get update
+#apt install software-properties-common -y
+#apt-get install wget -y
+
+#wget -q http://emacs.ganneff.de/apt.key -O- | apt-key add
+
+#add-apt-repository "deb http://emacs.ganneff.de/ stretch main"
+#apt-get update
+#apt-get install emacs-snapshot -y
+#update-alternatives --config emacsclient
+
+# ===============================
+
+# install TREXIO in the container from the GitHub repo clone
+
+#apt-get install git -y
+
+#git clone https://github.com/TREX-CoE/trexio.git
+#cd trexio
+#git checkout swig-python
+
+#./autogen.sh
+#TREXIO_DEVEL=1 ./configure --enable-silent-rules
+#make
+#make check
+
+# ===============================
+
+# alternatively: build wheel directly from developer-provided .tar.gz of TREXIO (generated with `python setup.py sdist`)
+# note: trexio-VERSION.tar.gz has to be in the root directory of the host machine
+
+# process input: first argument is the name of the .tar.gz with the source code of the Python API
+if [[ -z "$1" ]]; then
+    echo "Please specify the name of the TREXIO source code distribution (with .tar.gz suffix)"
+    exit 1
+fi
+
+TREXIO_SOURCE=${1}
+
+# remove prefix that ends with "-"
+tmp=${TREXIO_SOURCE#*-}
+# remove suffix that ends with ".tar.gz"
+TR_VERSION=${tmp%.tar.gz*}
+
+echo "TREXIO VERSION:" ${TR_VERSION}
+
+# unzip and enter the folder with TREXIO Python API
+gzip -cd /tmp/trexio-${TR_VERSION}.tar.gz | tar xvf -
+cd trexio-${TR_VERSION}
+
+# create and activate a virtual environment based on CPython version 3.6
+/opt/python/cp36-cp36m/bin/python3 -m venv --clear trexio-manylinux-py36
+source trexio-manylinux-py36/bin/activate 
+python3 --version
+
+# upgrade pip, otherwise it complains that manylinux wheel is "...not supported wheel on this platform"
+pip install --upgrade pip 
+# install dependencies needed to build manylinux wheel
+pip install --upgrade setuptools wheel auditwheel numpy
+
+# set an environment variable needed to locate numpy header files
+source tools/set_NUMPY_INCLUDEDIR.sh
+
+# produce conventional (non-manylinux) wheel
+python3 setup.py bdist_wheel
+
+# use auditwheel from PyPA to repair all wheels and make them manylinux-compatible
+auditwheel repair dist/trexio-${TR_VERSION}-cp36-cp36m-*.whl
+
+# install the produced manylinux wheel in the virtual environment
+python3 -m pip install wheelhouse/trexio-${TR_VERSION}-cp36-cp36m-manylinux*.whl
+
+# run test script
+cd test && python3 test_api.py && cd ..
+
+# cleaning
+rm -rf -- dist/ build/ trexio.egg-info/
+
+# deactivate the current environment
+deactivate
+
+# create and activate a virtual environment based on CPython version 3.7
+/opt/python/cp37-cp37m/bin/python3 -m venv --clear trexio-manylinux-py37
+source trexio-manylinux-py37/bin/activate 
+python3 --version
+
+# upgrade pip, otherwise it complains that manylinux wheel is "...not supported wheel on this platform"
+pip install --upgrade pip 
+# install dependencies needed to build manylinux wheel
+pip install --upgrade setuptools wheel auditwheel numpy
+
+# set an environment variable needed to locate numpy header files
+source tools/set_NUMPY_INCLUDEDIR.sh
+
+# produce conventional (non-manylinux) wheel
+python3 setup.py bdist_wheel
+
+# use auditwheel from PyPA to repair all wheels and make them manylinux-compatible
+auditwheel repair dist/trexio-${TR_VERSION}-cp37-cp37m-*.whl
+
+# install the produced manylinux wheel in the virtual environment
+python3 -m pip install wheelhouse/trexio-${TR_VERSION}-cp37-cp37m-manylinux*.whl
+
+# run test script
+cd test && python3 test_api.py && cd ..
+
+# cleaning
+rm -rf -- dist/ build/ trexio.egg-info/
+
+# deactivate the current environment
+deactivate
+
+# create and activate a virtual environment based on CPython version 3.8
+# NOTE: starting from CPython 3.8 there is no need to add m in the abi-tag, e.g. use cp38-cp38 instead of cp38-cp38m
+/opt/python/cp38-cp38/bin/python3 -m venv --clear trexio-manylinux-py38
+source trexio-manylinux-py38/bin/activate 
+python3 --version
+
+# upgrade pip, otherwise it complains that manylinux wheel is "...not supported wheel on this platform"
+pip install --upgrade pip 
+# install dependencies needed to build manylinux wheel
+pip3 install --upgrade setuptools wheel auditwheel numpy
+
+# set an environment variable needed to locate numpy header files
+source tools/set_NUMPY_INCLUDEDIR.sh
+
+# produce conventional (non-manylinux) wheel
+python3 setup.py bdist_wheel
+
+# use auditwheel from PyPA to repair all wheels and make them manylinux-compatible
+auditwheel repair dist/trexio-${TR_VERSION}-cp38-cp38-*.whl
+
+# install the produced manylinux wheel in the virtual environment
+python3 -m pip install wheelhouse/trexio-${TR_VERSION}-cp38-cp38-manylinux*.whl
+
+# run test script
+cd test && python3 test_api.py && cd ..
+
+# cleaning
+rm -rf -- dist/ build/ trexio.egg-info/
+
+# deactivate the current environment
+deactivate
+
+# create and activate a virtual environment based on CPython version 3.8
+/opt/python/cp39-cp39/bin/python3 -m venv --clear trexio-manylinux-py39
+source trexio-manylinux-py39/bin/activate 
+python3 --version
+
+# upgrade pip, otherwise it complains that manylinux wheel is "...not supported wheel on this platform"
+pip install --upgrade pip 
+# install dependencies needed to build manylinux wheel
+pip3 install --upgrade setuptools wheel auditwheel numpy
+
+# produce conventional (non-manylinux) wheel
+python3 setup.py bdist_wheel
+
+# use auditwheel from PyPA to repair all wheels and make them manylinux-compatible
+auditwheel repair dist/trexio-${TR_VERSION}-cp39-cp39-*.whl
+
+# install the produced manylinux wheel in the virtual environment
+python3 -m pip install wheelhouse/trexio-${TR_VERSION}-cp39-cp39-manylinux*.whl
+
+# run test script
+cd test && python3 test_api.py && cd ..
+
+# cleaning
+rm -rf -- dist/ build/ trexio.egg-info/
+
+# deactivate the current environment
+deactivate
+
+# remove all virtual environments used to produce the wheels
+rm -rf -- trexio-manylinux-py39 \
+	  trexio-manylinux-py38 \
+	  trexio-manylinux-py37 \
+	  trexio-manylinux-py36 
+
diff --git a/m4/ax_pkg_swig.m4 b/m4/ax_pkg_swig.m4
new file mode 100644
index 00000000..b1f77f0c
--- /dev/null
+++ b/m4/ax_pkg_swig.m4
@@ -0,0 +1,139 @@
+# ===========================================================================
+#       https://www.gnu.org/software/autoconf-archive/ax_pkg_swig.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PKG_SWIG([major.minor.micro], [action-if-found], [action-if-not-found])
+#
+# DESCRIPTION
+#
+#   This macro searches for a SWIG installation on your system. If found,
+#   then SWIG is AC_SUBST'd; if not found, then $SWIG is empty.  If SWIG is
+#   found, then SWIG_LIB is set to the SWIG library path, and AC_SUBST'd.
+#
+#   You can use the optional first argument to check if the version of the
+#   available SWIG is greater than or equal to the value of the argument. It
+#   should have the format: N[.N[.N]] (N is a number between 0 and 999. Only
+#   the first N is mandatory.) If the version argument is given (e.g.
+#   1.3.17), AX_PKG_SWIG checks that the swig package is this version number
+#   or higher.
+#
+#   As usual, action-if-found is executed if SWIG is found, otherwise
+#   action-if-not-found is executed.
+#
+#   In configure.in, use as:
+#
+#     AX_PKG_SWIG(1.3.17, [], [ AC_MSG_ERROR([SWIG is required to build..]) ])
+#     AX_SWIG_ENABLE_CXX
+#     AX_SWIG_MULTI_MODULE_SUPPORT
+#     AX_SWIG_PYTHON
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de>
+#   Copyright (c) 2008 Alan W. Irwin
+#   Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net>
+#   Copyright (c) 2008 Andrew Collier
+#   Copyright (c) 2011 Murray Cumming <murrayc@openismus.com>
+#   Copyright (c) 2021 Vincent Danjean <Vincent.Danjean@ens-lyon.org>
+#
+#   This program is free software; you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation; either version 2 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 14
+
+AC_DEFUN([AX_PKG_SWIG],[
+        # Find path to the "swig" executable.
+        AC_PATH_PROGS([SWIG],[swig swig3.0 swig2.0])
+        if test -z "$SWIG" ; then
+                m4_ifval([$3],[$3],[:])
+        elif test -z "$1" ; then
+                m4_ifval([$2],[$2],[:])
+	else
+                AC_MSG_CHECKING([SWIG version])
+                [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`]
+                AC_MSG_RESULT([$swig_version])
+                if test -n "$swig_version" ; then
+                        # Calculate the required version number components
+                        [required=$1]
+                        [required_major=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_major" ; then
+                                [required_major=0]
+                        fi
+                        [required=`echo $required. | sed 's/[0-9]*[^0-9]//'`]
+                        [required_minor=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_minor" ; then
+                                [required_minor=0]
+                        fi
+                        [required=`echo $required. | sed 's/[0-9]*[^0-9]//'`]
+                        [required_patch=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_patch" ; then
+                                [required_patch=0]
+                        fi
+                        # Calculate the available version number components
+                        [available=$swig_version]
+                        [available_major=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_major" ; then
+                                [available_major=0]
+                        fi
+                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+                        [available_minor=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_minor" ; then
+                                [available_minor=0]
+                        fi
+                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+                        [available_patch=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_patch" ; then
+                                [available_patch=0]
+                        fi
+                        # Convert the version tuple into a single number for easier comparison.
+                        # Using base 100 should be safe since SWIG internally uses BCD values
+                        # to encode its version number.
+                        required_swig_vernum=`expr $required_major \* 10000 \
+                            \+ $required_minor \* 100 \+ $required_patch`
+                        available_swig_vernum=`expr $available_major \* 10000 \
+                            \+ $available_minor \* 100 \+ $available_patch`
+
+                        if test $available_swig_vernum -lt $required_swig_vernum; then
+                                AC_MSG_WARN([SWIG version >= $1 is required.  You have $swig_version.])
+                                SWIG=''
+                                m4_ifval([$3],[$3],[])
+                        else
+                                AC_MSG_CHECKING([for SWIG library])
+                                SWIG_LIB=`$SWIG -swiglib`
+                                AC_MSG_RESULT([$SWIG_LIB])
+                                m4_ifval([$2],[$2],[])
+                        fi
+                else
+                        AC_MSG_WARN([cannot determine SWIG version])
+                        SWIG=''
+                        m4_ifval([$3],[$3],[])
+                fi
+        fi
+        AC_SUBST([SWIG_LIB])
+])
+
diff --git a/python/.gitignore b/python/.gitignore
new file mode 100644
index 00000000..f1c80461
--- /dev/null
+++ b/python/.gitignore
@@ -0,0 +1,8 @@
+AUTHORS
+LICENSE
+src/
+
+build/
+dist/
+*.egg-info/
+__pycache__/
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
new file mode 100644
index 00000000..7c16f427
--- /dev/null
+++ b/python/MANIFEST.in
@@ -0,0 +1,9 @@
+include src/*.c 
+include src/trexio*.h 
+include examples/notebooks/* 
+include examples/README.md 
+include requirements.txt tools/set_NUMPY_INCLUDEDIR.sh
+
+exclude examples/LICENSE
+exclude examples/requirements.txt
+exclude examples/runtime.txt
diff --git a/python/README.md b/python/README.md
new file mode 100644
index 00000000..b3e3b06a
--- /dev/null
+++ b/python/README.md
@@ -0,0 +1,97 @@
+
+# TREXIO Python API
+
+[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/TREX-CoE/trexio-tutorials/HEAD)
+
+TREXIO provides a Python API, which enables interactive calls to the library.
+It facilitates the development of interfaces between different codes and 
+can be used to convert data from one input/output file format into another.
+
+
+## Requirements
+
+- python3 	(>= 3.6)
+- numpy
+- C compiler 	(gcc/icc)
+
+
+## Installation from PyPI
+
+In short, you can run the following command:
+
+`pip install trexio`
+
+However, it is good practice to first check for updates of the build-system packages. This can be achieved by running
+
+`python3 -m pip install --upgrade pip setuptools wheel`
+
+**Note: we highly recommend to use virtual environments to avoid compatibility issues and to improve reproducibility.** 
+For more details, see the corresponding part of the [Python documentation](https://docs.python.org/3/library/venv.html#creating-virtual-environments).
+
+
+## Additional requirements (for installation from source)
+
+- HDF5 library	(>= 1.8)
+- pkgconfig     (Python package)
+
+
+## Installation from source
+
+1. Download the `trexio-<version>.tar.gz` file with the latest Python API
+2. `gzip -cd trexio-<version>.tar.gz | tar xvf -`
+3. `cd trexio-<version>`
+4. `pip3 install -r requirements.txt` (this installs all required python dependencies)
+5. Export custom environment variables needed for the installation following the procedure below and replacing `/path/to/hdf5/` with your paths. 
+Steps (i) and (ii) can be skipped if HDF5 is properly configured for `pkg-config` (i.e. if executing `pkg-config --libs hdf5` returns a list of options).
+   1. `export H5_CFLAGS=-I/path/to/hdf5/include`
+   2. `export H5_LDFLAGS=-L/path/to/hdf5/lib`
+   3. `source tools/set_NUMPY_INCLUDEDIR.sh`
+6. `pip3 install .` (this installs `trexio` in your environment)
+7. `cd test && python3 test_api.py` (this executes several tests that verify the installation)
+
+You are ready to go!
+
+**Note:** 
+installation based on `pip` compiles its own C extension (shared library) called `pytrexio`. 
+This extension is built from the TREXIO source files coupled to the wrapper code generated by [SWIG](http://www.swig.org/).
+The compiler options during this installation may differ from the ones used to compile the primary TREXIO API in C.
+Furthermore, custom compiler flags provided to `./configure` or `make` are not applied to the Python API.
+
+
+## Examples
+
+An interactive Jupyter notebook called `tutorial_benzene.ipynb` is provided in the `examples` directory. 
+The notebook can be lauched either locally (see [next section](#Running-the-notebook) for details) or using [pre-built environment on Binder](https://mybinder.org/v2/gh/TREX-CoE/trexio-tutorials/HEAD?filepath=notebooks%2Ftutorial_benzene.ipynb).
+
+Jupyter can be installed using `pip install jupyter`. If you are not familiar with it, feel free to consult the [Jupyter documentation](https://jupyter-notebook.readthedocs.io/en/stable/notebook.html).
+
+
+### Running the notebook
+
+The example notebook can be launched using the following command:
+
+`jupyter notebook tutorial_benzene.ipynb`
+
+
+### Additional steps needed to run a custom virtual environment in Jupyter notebooks
+
+In some cases, it may happen that the Jupyter kernels in the activated virtual environment (e.g. `myvenv`) still point to the system-wide python binaries and not to the environment ones.
+This will result in `ImportError` when importing `trexio` in the notebook cell. In order to avoid this, the `myvenv` has to be installed as an additional kernel.
+This requires `ipykernel` python package, which usually comes together with the Jupyter installation. If this is not the case, run `pip install ipykernel`. 
+You can install `myvenv` as a kernel by executing the following command:
+
+`python3 -m ipykernel install --user --name=myvenv`
+
+Now you can launch a Jupyter notebook. Once it is open, make sure that your virtual environment is selected as the current kernel. 
+If this is not the case, try this:
+
+1. Press the `Kernel` button in the navigation panel
+2. In the output list of options select `Change kernel`
+3. Find the name of your virtual environment (e.g. `myvenv`) in the list and select it
+
+That's it, you have activated the custom virtual environment called `myvenv` in your notebook.
+
+To uninstall the kernel named `myvenv`, execute the following command:
+
+`jupyter kernelspec uninstall myvenv`
+
diff --git a/python/examples b/python/examples
new file mode 160000
index 00000000..b6316196
--- /dev/null
+++ b/python/examples
@@ -0,0 +1,1 @@
+Subproject commit b631619698a4b608a8778c75a9a359731bfa682a
diff --git a/python/install_pytrexio.sh b/python/install_pytrexio.sh
new file mode 100755
index 00000000..1b7c314b
--- /dev/null
+++ b/python/install_pytrexio.sh
@@ -0,0 +1,77 @@
+#!/bin/bash                                                    
+
+set -x
+set -e
+
+# the parser below is needed when ./configure outputs several HDF5-related flags, which are then provided as input arguments to this script
+for arg in "$@"
+do
+    if [[ $arg == "-L"* ]] && [[ $arg == *"hdf5"* ]]; then
+        H5_LDFLAGS_LOCAL=$arg
+    elif [[ $arg == "-I"* ]] && [[ $arg == *"hdf5"* ]]; then
+        H5_CFLAGS_LOCAL=$arg
+    fi
+done
+
+# check that both variables are set
+if [[ -z ${H5_LDFLAGS_LOCAL} ]] || [[ -z ${H5_CFLAGS_LOCAL} ]]; then
+    if [[ -z ${H5_LDFLAGS} ]] || [[ -z ${H5_CFLAGS} ]]; then
+        echo "Paths to the HDF5 installation are not provided. pkgconfig will try to detect them."
+    else
+        echo "Using exported H5_LDFLAGS and H5_CFLAGS environment variables."
+    fi
+else
+    export H5_LDFLAGS=${H5_LDFLAGS_LOCAL}
+    export H5_CFLAGS=${H5_CFLAGS_LOCAL}
+fi
+
+# Install/upgrade packages required for the installation
+python3 -m pip install --upgrade setuptools wheel twine
+python3 -m pip install -r requirements.txt
+
+# export NUMPY_INCLUDEDIR environment variable needed for the proper setup
+source tools/set_NUMPY_INCLUDEDIR.sh
+
+if [[ -z ${NUMPY_INCLUDEDIR} ]] ; then
+    echo "NUMPY_INCLUDEDIR is not set. Check that numpy is installed (e.g. call pip freeze)."
+    exit 1
+fi
+
+# Create build directory and compile extension files (*.c)
+# --no-user-cfg disables custom .cfg files of the user machine, so that only setup.cfg is used
+python3 -s setup.py --no-user-cfg build 
+
+# Local inplace build of the .so module with SWIG-produced pytrexio_wrap.c (from the SWIG documentation)
+#python3 setup.py build_ext --inplace --swig-opts="-modern"
+
+# Create distributions: 
+#   1) sdist produces .tar.gz with all files necessary for manual compilation;
+#   2) bdist_whell produces .whl wheel distribution (see https://www.python.org/dev/peps/pep-0425/).
+python3 setup.py sdist bdist_wheel
+
+# Install pytrexio in the current environment from the aforementioned wheel
+# --force-reinstall is needed here because build-system pre-installs pytrexio in the environment 
+#                   but does not install things in the corresponding site-packages directory
+python3 -m pip install dist/trexio-*.whl --force-reinstall
+
+# Run the command below in the root directory to install the package in 'editable' (-e) mode without dependencies (--no-deps)
+#python -m pip install -e . --no-deps
+
+# Uninstall pytrexio: this only works from the pytrexio root directory
+#python3 -m pip uninstall trexio
+
+# Test the current release by uploading to TestPyPI sandbox
+#python3 -m twine upload --repository testpypi dist/trexio-*.tar.gz
+
+# Upload updated version of PyTREXIO to PyPI
+#python3 -m twine upload dist/trexio-*.tar.gz
+
+# Cleaning
+rm -rf build dist trexio.egg-info
+
+# Additional information related to the installation of the TREXIO Python API
+
+#Removing MANIFEST.in leads to issues in the installation. In particular, the .c and .h source files do not get copied 
+#from the src/ directory into the tar.gz which is produced by setup sdist command. 
+#These source files are required to build the pytrexio.so extension module, which is needed for the Python API.
+
diff --git a/python/pyproject.toml b/python/pyproject.toml
new file mode 100644
index 00000000..fa03e4c4
--- /dev/null
+++ b/python/pyproject.toml
@@ -0,0 +1,7 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "wheel",
+    "pkgconfig"
+]
+build-backend = "setuptools.build_meta"
diff --git a/python/pytrexio/__init__.py b/python/pytrexio/__init__.py
new file mode 100644
index 00000000..415bcc4e
--- /dev/null
+++ b/python/pytrexio/__init__.py
@@ -0,0 +1,1 @@
+#from ._version import __version__
diff --git a/python/pytrexio/_version.py b/python/pytrexio/_version.py
new file mode 100644
index 00000000..3dc1f76b
--- /dev/null
+++ b/python/pytrexio/_version.py
@@ -0,0 +1,1 @@
+__version__ = "0.1.0"
diff --git a/python/requirements.txt b/python/requirements.txt
new file mode 100644
index 00000000..e7bbedf9
--- /dev/null
+++ b/python/requirements.txt
@@ -0,0 +1,3 @@
+setuptools>=42
+pkgconfig
+numpy
diff --git a/python/setup.cfg b/python/setup.cfg
new file mode 100644
index 00000000..b4d9c49b
--- /dev/null
+++ b/python/setup.cfg
@@ -0,0 +1,11 @@
+[metadata]
+long_description = file: README.md
+licence_file = LICENSE
+project_urls =
+    Bug Tracker = https://github.com/TREX-CoE/trexio/issues
+license = BSD 3-Clause License
+
+[options]
+zip_safe = False
+python_requires = >=3.6
+
diff --git a/python/setup.py b/python/setup.py
new file mode 100644
index 00000000..839b957c
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+"""
+setup.py file for TREXIO Python package
+"""
+
+
+import os
+from setuptools import setup, Extension
+
+# this was recommended to solve the problem of the missing numpy header files
+# bit it causes `pip install .` to fail with numpy module not found error
+#try:
+#    import numpy
+#except ImportError:
+#    raise Exception("numpy Python package cannot be imported.")
+#numpy_includedir = numpy.get_include()
+
+# this does not cause aforementioned issue but the includedir points to system-wide numpy and not to venv-wide
+#from distutils.sysconfig import get_python_inc
+#numpy_includedir = os.path.join(get_python_inc(plat_specific=1), 'numpy')
+
+# dirty workaround: get numpy includedir from the environment variable that can be pre-set using set_NUMPY_INCLUDEDIR.sh
+numpy_includedir = os.environ.get("NUMPY_INCLUDEDIR", None)
+numpy_isUndefined = numpy_includedir is None or numpy_includedir==""
+
+if numpy_isUndefined:
+    raise Exception("NUMPY_INCLUDEDIR environment variable is not specified. Please do it manually or execute set_NUMPY_INCLUDEDIR.sh script.")
+
+
+rootpath = os.path.dirname(os.path.abspath(__file__))
+srcpath = os.path.join(rootpath, 'src')
+c_files = ['trexio.c', 'trexio_hdf5.c', 'trexio_text.c', 'pytrexio_wrap.c']
+
+
+with open("README.md", "r") as fh:
+    long_description = fh.read()
+
+VERSIONFILE = "pytrexio/_version.py"
+try:
+    exec(open(VERSIONFILE).read())
+except:
+    raise IOError("Could not open the version file %s." % (VERSIONFILE, ))
+
+version_r = __version__
+if not version_r:
+    raise RuntimeError("Unable to find a version string in %s." % (VERSIONFILE, ))
+
+
+# =========================== Start of the HDF5 block =========================== #
+# The block below is needed to derive additional flags related to the HDF5 library,
+# which is required to build pytrexio extension module during the setup.py execution
+
+h5_ldflags_withl = os.environ.get("H5_LDFLAGS", None)
+h5_cflags_withI  = os.environ.get("H5_CFLAGS", None)
+
+h5_ldflags_isUndefined = h5_ldflags_withl is None or h5_ldflags_withl==""
+h5_cflags_isUndefined = h5_cflags_withI is None or h5_cflags_withI==""
+
+if h5_ldflags_isUndefined or h5_cflags_isUndefined:
+
+    try:
+        import pkgconfig as pk
+    except ImportError:
+        raise Exception("pkgconfig Python package cannot be imported.")
+
+    try:
+        assert pk.exists('hdf5')
+    except AssertionError:
+        raise Exception("pkg-config could not locate HDF5")
+
+    h5_cflags_withI = pk.cflags('hdf5')
+    h5_ldflags_withl = pk.libs('hdf5')
+
+h5_cflags = h5_cflags_withI.replace("-I","").split(" ")[0]
+h5_ldflags = h5_ldflags_withl.split(" ")[0]
+
+# ============================ End of the HDF5 block ============================ #
+
+# Define pytrexio extension module based on TREXIO source codes + SWIG-generated wrapper
+pytrexio_module = Extension('pytrexio._pytrexio',
+                            sources = [os.path.join(srcpath, code) for code in c_files],
+                            include_dirs = [h5_cflags, srcpath, numpy_includedir],
+                            libraries = ['hdf5', 'hdf5_hl'],
+                            extra_compile_args = ['-Wno-discarded-qualifiers'],
+                            extra_link_args = [h5_ldflags]
+                            )
+
+
+setup(name             = 'trexio',
+      version          = version_r,
+      author           = "TREX-CoE",
+      author_email     = "posenitskiy@irsamc.ups-tlse.fr",
+      description      = """Python API of the TREXIO library""",
+      long_description = long_description,
+      long_description_content_type = "text/markdown",
+      ext_modules      = [pytrexio_module],
+      py_modules       = ['trexio'],
+      packages         = ['pytrexio'],
+      url              = 'https://github.com/TREX-CoE/trexio',
+      license          = 'BSD',
+      classifiers=[
+         "Intended Audience :: Science/Research",
+         "Intended Audience :: Developers",
+         "Topic :: Scientific/Engineering",
+         "Programming Language :: C",
+         "Programming Language :: Python",
+         "Programming Language :: Python :: 3",
+         "Programming Language :: Python :: 3 :: Only",
+         "Programming Language :: Python :: Implementation :: CPython",
+         "License :: OSI Approved :: BSD License",
+         "Operating System :: POSIX",
+         "Operating System :: Unix",
+         "Operating System :: MacOS"
+      ],
+      python_requires = ">=3.6",
+      setup_requires = ['numpy', 'pkgconfig'],
+      install_requires = ['numpy']
+      )
+
diff --git a/python/tools/set_NUMPY_INCLUDEDIR.sh b/python/tools/set_NUMPY_INCLUDEDIR.sh
new file mode 100755
index 00000000..1c7ba4fb
--- /dev/null
+++ b/python/tools/set_NUMPY_INCLUDEDIR.sh
@@ -0,0 +1,4 @@
+
+INCLUDEDIR=`python -c 'import numpy; print(numpy.get_include())'`
+export NUMPY_INCLUDEDIR=${INCLUDEDIR}
+
diff --git a/src/.gitignore b/src/.gitignore
index c485d0b1..8847478b 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -2,6 +2,7 @@ templates_front/*.c
 templates_front/*.h
 templates_front/*.f90
 templates_front/*.fh_90
+templates_front/*.py
 templates_front/*.dump
 templates_front/populated/
 
@@ -15,20 +16,21 @@ templates_text/*.h
 templates_text/*.dump
 templates_text/populated/
 
-libtrexio.so
+*.so
 *.o
 *.h
 *.dump
 *.cppcheck
+
 trexio.c
 trexio_text.c
 trexio_hdf5.c
 trexio_f.f90
 trexio.mod
 
-test_c
-test_f
-*.h5
-trexio_test/
-trexio_test_fort/
+pytrexio.py
+pytrexio_wrap.c
 
+*.h5
+*.dir/
+__pycache__/
diff --git a/src/numpy.i b/src/numpy.i
new file mode 100644
index 00000000..99ed073a
--- /dev/null
+++ b/src/numpy.i
@@ -0,0 +1,3028 @@
+/* -*- C -*-  (not really, but good for syntax highlighting) */
+
+/*
+ * Copyright (c) 2005-2015, NumPy Developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials provided
+ *        with the distribution.
+ *
+ *     * Neither the name of the NumPy Developers nor the names of any
+ *        contributors may be used to endorse or promote products derived
+ *        from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef SWIGPYTHON
+
+%{
+#ifndef SWIG_FILE_WITH_INIT
+#define NO_IMPORT_ARRAY
+#endif
+#include "stdio.h"
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+#include <numpy/arrayobject.h>
+%}
+
+/**********************************************************************/
+
+%fragment("NumPy_Backward_Compatibility", "header")
+{
+%#if NPY_API_VERSION < 0x00000007
+%#define NPY_ARRAY_DEFAULT NPY_DEFAULT
+%#define NPY_ARRAY_FARRAY  NPY_FARRAY
+%#define NPY_FORTRANORDER  NPY_FORTRAN
+%#endif
+}
+
+/**********************************************************************/
+
+/* The following code originally appeared in
+ * enthought/kiva/agg/src/numeric.i written by Eric Jones.  It was
+ * translated from C++ to C by John Hunter.  Bill Spotz has modified
+ * it to fix some minor bugs, upgrade from Numeric to numpy (all
+ * versions), add some comments and functionality, and convert from
+ * direct code insertion to SWIG fragments.
+ */
+
+%fragment("NumPy_Macros", "header")
+{
+/* Macros to extract array attributes.
+ */
+%#if NPY_API_VERSION < 0x00000007
+%#define is_array(a)            ((a) && PyArray_Check((PyArrayObject*)a))
+%#define array_type(a)          (int)(PyArray_TYPE((PyArrayObject*)a))
+%#define array_numdims(a)       (((PyArrayObject*)a)->nd)
+%#define array_dimensions(a)    (((PyArrayObject*)a)->dimensions)
+%#define array_size(a,i)        (((PyArrayObject*)a)->dimensions[i])
+%#define array_strides(a)       (((PyArrayObject*)a)->strides)
+%#define array_stride(a,i)      (((PyArrayObject*)a)->strides[i])
+%#define array_data(a)          (((PyArrayObject*)a)->data)
+%#define array_descr(a)         (((PyArrayObject*)a)->descr)
+%#define array_flags(a)         (((PyArrayObject*)a)->flags)
+%#define array_clearflags(a,f)  (((PyArrayObject*)a)->flags) &= ~f
+%#define array_enableflags(a,f) (((PyArrayObject*)a)->flags) = f
+%#define array_is_fortran(a)    (PyArray_ISFORTRAN((PyArrayObject*)a))
+%#else
+%#define is_array(a)            ((a) && PyArray_Check(a))
+%#define array_type(a)          PyArray_TYPE((PyArrayObject*)a)
+%#define array_numdims(a)       PyArray_NDIM((PyArrayObject*)a)
+%#define array_dimensions(a)    PyArray_DIMS((PyArrayObject*)a)
+%#define array_strides(a)       PyArray_STRIDES((PyArrayObject*)a)
+%#define array_stride(a,i)      PyArray_STRIDE((PyArrayObject*)a,i)
+%#define array_size(a,i)        PyArray_DIM((PyArrayObject*)a,i)
+%#define array_data(a)          PyArray_DATA((PyArrayObject*)a)
+%#define array_descr(a)         PyArray_DESCR((PyArrayObject*)a)
+%#define array_flags(a)         PyArray_FLAGS((PyArrayObject*)a)
+%#define array_enableflags(a,f) PyArray_ENABLEFLAGS((PyArrayObject*)a,f)
+%#define array_clearflags(a,f)  PyArray_CLEARFLAGS((PyArrayObject*)a,f)
+%#define array_is_fortran(a)    (PyArray_IS_F_CONTIGUOUS((PyArrayObject*)a))
+%#endif
+%#define array_is_contiguous(a) (PyArray_ISCONTIGUOUS((PyArrayObject*)a))
+%#define array_is_native(a)     (PyArray_ISNOTSWAPPED((PyArrayObject*)a))
+}
+
+/**********************************************************************/
+
+%fragment("NumPy_Utilities",
+          "header")
+{
+  /* Given a PyObject, return a string describing its type.
+   */
+  const char* pytype_string(PyObject* py_obj)
+  {
+    if (py_obj == NULL          ) return "C NULL value";
+    if (py_obj == Py_None       ) return "Python None" ;
+    if (PyCallable_Check(py_obj)) return "callable"    ;
+    if (PyBytes_Check(   py_obj)) return "string"      ;
+    if (PyLong_Check(    py_obj)) return "int"         ;
+    if (PyFloat_Check(   py_obj)) return "float"       ;
+    if (PyDict_Check(    py_obj)) return "dict"        ;
+    if (PyList_Check(    py_obj)) return "list"        ;
+    if (PyTuple_Check(   py_obj)) return "tuple"       ;
+
+    return "unknown type";
+  }
+
+  /* Given a NumPy typecode, return a string describing the type.
+   */
+  const char* typecode_string(int typecode)
+  {
+    static const char* type_names[25] = {"bool",
+                                         "byte",
+                                         "unsigned byte",
+                                         "short",
+                                         "unsigned short",
+                                         "int",
+                                         "unsigned int",
+                                         "long",
+                                         "unsigned long",
+                                         "long long",
+                                         "unsigned long long",
+                                         "float",
+                                         "double",
+                                         "long double",
+                                         "complex float",
+                                         "complex double",
+                                         "complex long double",
+                                         "object",
+                                         "string",
+                                         "unicode",
+                                         "void",
+                                         "ntypes",
+                                         "notype",
+                                         "char",
+                                         "unknown"};
+    return typecode < 24 ? type_names[typecode] : type_names[24];
+  }
+
+  /* Make sure input has correct numpy type.  This now just calls
+     PyArray_EquivTypenums().
+   */
+  int type_match(int actual_type,
+                 int desired_type)
+  {
+    return PyArray_EquivTypenums(actual_type, desired_type);
+  }
+
+%#ifdef SWIGPY_USE_CAPSULE
+  void free_cap(PyObject * cap)
+  {
+    void* array = (void*) PyCapsule_GetPointer(cap,SWIGPY_CAPSULE_NAME);
+    if (array != NULL) free(array);
+  }
+%#endif
+
+
+}
+
+/**********************************************************************/
+
+%fragment("NumPy_Object_to_Array",
+          "header",
+          fragment="NumPy_Backward_Compatibility",
+          fragment="NumPy_Macros",
+          fragment="NumPy_Utilities")
+{
+  /* Given a PyObject pointer, cast it to a PyArrayObject pointer if
+   * legal.  If not, set the python error string appropriately and
+   * return NULL.
+   */
+  PyArrayObject* obj_to_array_no_conversion(PyObject* input,
+                                            int        typecode)
+  {
+    PyArrayObject* ary = NULL;
+    if (is_array(input) && (typecode == NPY_NOTYPE ||
+                            PyArray_EquivTypenums(array_type(input), typecode)))
+    {
+      ary = (PyArrayObject*) input;
+    }
+    else if is_array(input)
+    {
+      const char* desired_type = typecode_string(typecode);
+      const char* actual_type  = typecode_string(array_type(input));
+      PyErr_Format(PyExc_TypeError,
+                   "Array of type '%s' required.  Array of type '%s' given",
+                   desired_type, actual_type);
+      ary = NULL;
+    }
+    else
+    {
+      const char* desired_type = typecode_string(typecode);
+      const char* actual_type  = pytype_string(input);
+      PyErr_Format(PyExc_TypeError,
+                   "Array of type '%s' required.  A '%s' was given",
+                   desired_type,
+                   actual_type);
+      ary = NULL;
+    }
+    return ary;
+  }
+
+  /* Convert the given PyObject to a NumPy array with the given
+   * typecode.  On success, return a valid PyArrayObject* with the
+   * correct type.  On failure, the python error string will be set and
+   * the routine returns NULL.
+   */
+  PyArrayObject* obj_to_array_allow_conversion(PyObject* input,
+                                               int       typecode,
+                                               int*      is_new_object)
+  {
+    PyArrayObject* ary = NULL;
+    PyObject*      py_obj;
+    if (is_array(input) && (typecode == NPY_NOTYPE ||
+                            PyArray_EquivTypenums(array_type(input),typecode)))
+    {
+      ary = (PyArrayObject*) input;
+      *is_new_object = 0;
+    }
+    else
+    {
+      py_obj = PyArray_FROMANY(input, typecode, 0, 0, NPY_ARRAY_DEFAULT);
+      /* If NULL, PyArray_FromObject will have set python error value.*/
+      ary = (PyArrayObject*) py_obj;
+      *is_new_object = 1;
+    }
+    return ary;
+  }
+
+  /* Given a PyArrayObject, check to see if it is contiguous.  If so,
+   * return the input pointer and flag it as not a new object.  If it is
+   * not contiguous, create a new PyArrayObject using the original data,
+   * flag it as a new object and return the pointer.
+   */
+  PyArrayObject* make_contiguous(PyArrayObject* ary,
+                                 int*           is_new_object,
+                                 int            min_dims,
+                                 int            max_dims)
+  {
+    PyArrayObject* result;
+    if (array_is_contiguous(ary))
+    {
+      result = ary;
+      *is_new_object = 0;
+    }
+    else
+    {
+      result = (PyArrayObject*) PyArray_ContiguousFromObject((PyObject*)ary,
+                                                              array_type(ary),
+                                                              min_dims,
+                                                              max_dims);
+      *is_new_object = 1;
+    }
+    return result;
+  }
+
+  /* Given a PyArrayObject, check to see if it is Fortran-contiguous.
+   * If so, return the input pointer, but do not flag it as not a new
+   * object.  If it is not Fortran-contiguous, create a new
+   * PyArrayObject using the original data, flag it as a new object
+   * and return the pointer.
+   */
+  PyArrayObject* make_fortran(PyArrayObject* ary,
+                              int*           is_new_object)
+  {
+    PyArrayObject* result;
+    if (array_is_fortran(ary))
+    {
+      result = ary;
+      *is_new_object = 0;
+    }
+    else
+    {
+      Py_INCREF(array_descr(ary));
+      result = (PyArrayObject*) PyArray_FromArray(ary,
+                                                  array_descr(ary),
+%#if NPY_API_VERSION < 0x00000007
+                                                  NPY_FORTRANORDER);
+%#else
+                                                  NPY_ARRAY_F_CONTIGUOUS);
+%#endif
+      *is_new_object = 1;
+    }
+    return result;
+  }
+
+  /* Convert a given PyObject to a contiguous PyArrayObject of the
+   * specified type.  If the input object is not a contiguous
+   * PyArrayObject, a new one will be created and the new object flag
+   * will be set.
+   */
+  PyArrayObject* obj_to_array_contiguous_allow_conversion(PyObject* input,
+                                                          int       typecode,
+                                                          int*      is_new_object)
+  {
+    int is_new1 = 0;
+    int is_new2 = 0;
+    PyArrayObject* ary2;
+    PyArrayObject* ary1 = obj_to_array_allow_conversion(input,
+                                                        typecode,
+                                                        &is_new1);
+    if (ary1)
+    {
+      ary2 = make_contiguous(ary1, &is_new2, 0, 0);
+      if ( is_new1 && is_new2)
+      {
+        Py_DECREF(ary1);
+      }
+      ary1 = ary2;
+    }
+    *is_new_object = is_new1 || is_new2;
+    return ary1;
+  }
+
+  /* Convert a given PyObject to a Fortran-ordered PyArrayObject of the
+   * specified type.  If the input object is not a Fortran-ordered
+   * PyArrayObject, a new one will be created and the new object flag
+   * will be set.
+   */
+  PyArrayObject* obj_to_array_fortran_allow_conversion(PyObject* input,
+                                                       int       typecode,
+                                                       int*      is_new_object)
+  {
+    int is_new1 = 0;
+    int is_new2 = 0;
+    PyArrayObject* ary2;
+    PyArrayObject* ary1 = obj_to_array_allow_conversion(input,
+                                                        typecode,
+                                                        &is_new1);
+    if (ary1)
+    {
+      ary2 = make_fortran(ary1, &is_new2);
+      if (is_new1 && is_new2)
+      {
+        Py_DECREF(ary1);
+      }
+      ary1 = ary2;
+    }
+    *is_new_object = is_new1 || is_new2;
+    return ary1;
+  }
+} /* end fragment */
+
+/**********************************************************************/
+
+%fragment("NumPy_Array_Requirements",
+          "header",
+          fragment="NumPy_Backward_Compatibility",
+          fragment="NumPy_Macros")
+{
+  /* Test whether a python object is contiguous.  If array is
+   * contiguous, return 1.  Otherwise, set the python error string and
+   * return 0.
+   */
+  int require_contiguous(PyArrayObject* ary)
+  {
+    int contiguous = 1;
+    if (!array_is_contiguous(ary))
+    {
+      PyErr_SetString(PyExc_TypeError,
+                      "Array must be contiguous.  A non-contiguous array was given");
+      contiguous = 0;
+    }
+    return contiguous;
+  }
+
+  /* Test whether a python object is (C_ or F_) contiguous.  If array is
+   * contiguous, return 1.  Otherwise, set the python error string and
+   * return 0.
+   */
+  int require_c_or_f_contiguous(PyArrayObject* ary)
+  {
+    int contiguous = 1;
+    if (!(array_is_contiguous(ary) || array_is_fortran(ary)))
+    {
+      PyErr_SetString(PyExc_TypeError,
+                      "Array must be contiguous (C_ or F_).  A non-contiguous array was given");
+      contiguous = 0;
+    }
+    return contiguous;
+  }
+
+  /* Require that a numpy array is not byte-swapped.  If the array is
+   * not byte-swapped, return 1.  Otherwise, set the python error string
+   * and return 0.
+   */
+  int require_native(PyArrayObject* ary)
+  {
+    int native = 1;
+    if (!array_is_native(ary))
+    {
+      PyErr_SetString(PyExc_TypeError,
+                      "Array must have native byteorder.  "
+                      "A byte-swapped array was given");
+      native = 0;
+    }
+    return native;
+  }
+
+  /* Require the given PyArrayObject to have a specified number of
+   * dimensions.  If the array has the specified number of dimensions,
+   * return 1.  Otherwise, set the python error string and return 0.
+   */
+  int require_dimensions(PyArrayObject* ary,
+                         int            exact_dimensions)
+  {
+    int success = 1;
+    if (array_numdims(ary) != exact_dimensions)
+    {
+      PyErr_Format(PyExc_TypeError,
+                   "Array must have %d dimensions.  Given array has %d dimensions",
+                   exact_dimensions,
+                   array_numdims(ary));
+      success = 0;
+    }
+    return success;
+  }
+
+  /* Require the given PyArrayObject to have one of a list of specified
+   * number of dimensions.  If the array has one of the specified number
+   * of dimensions, return 1.  Otherwise, set the python error string
+   * and return 0.
+   */
+  int require_dimensions_n(PyArrayObject* ary,
+                           int*           exact_dimensions,
+                           int            n)
+  {
+    int success = 0;
+    int i;
+    char dims_str[255] = "";
+    char s[255];
+    for (i = 0; i < n && !success; i++)
+    {
+      if (array_numdims(ary) == exact_dimensions[i])
+      {
+        success = 1;
+      }
+    }
+    if (!success)
+    {
+      for (i = 0; i < n-1; i++)
+      {
+        sprintf(s, "%d, ", exact_dimensions[i]);
+        strcat(dims_str,s);
+      }
+      sprintf(s, " or %d", exact_dimensions[n-1]);
+      strcat(dims_str,s);
+      PyErr_Format(PyExc_TypeError,
+                   "Array must have %s dimensions.  Given array has %d dimensions",
+                   dims_str,
+                   array_numdims(ary));
+    }
+    return success;
+  }
+
+  /* Require the given PyArrayObject to have a specified shape.  If the
+   * array has the specified shape, return 1.  Otherwise, set the python
+   * error string and return 0.
+   */
+  int require_size(PyArrayObject* ary,
+                   npy_intp*      size,
+                   int            n)
+  {
+    int i;
+    int success = 1;
+    size_t len;
+    char desired_dims[255] = "[";
+    char s[255];
+    char actual_dims[255] = "[";
+    for(i=0; i < n;i++)
+    {
+      if (size[i] != -1 &&  size[i] != array_size(ary,i))
+      {
+        success = 0;
+      }
+    }
+    if (!success)
+    {
+      for (i = 0; i < n; i++)
+      {
+        if (size[i] == -1)
+        {
+          sprintf(s, "*,");
+        }
+        else
+        {
+          sprintf(s, "%ld,", (long int)size[i]);
+        }
+        strcat(desired_dims,s);
+      }
+      len = strlen(desired_dims);
+      desired_dims[len-1] = ']';
+      for (i = 0; i < n; i++)
+      {
+        sprintf(s, "%ld,", (long int)array_size(ary,i));
+        strcat(actual_dims,s);
+      }
+      len = strlen(actual_dims);
+      actual_dims[len-1] = ']';
+      PyErr_Format(PyExc_TypeError,
+                   "Array must have shape of %s.  Given array has shape of %s",
+                   desired_dims,
+                   actual_dims);
+    }
+    return success;
+  }
+
+  /* Require the given PyArrayObject to to be Fortran ordered.  If the
+   * the PyArrayObject is already Fortran ordered, do nothing.  Else,
+   * set the Fortran ordering flag and recompute the strides.
+   */
+  int require_fortran(PyArrayObject* ary)
+  {
+    int success = 1;
+    int nd = array_numdims(ary);
+    int i;
+    npy_intp * strides = array_strides(ary);
+    if (array_is_fortran(ary)) return success;
+    int n_non_one = 0;
+    /* Set the Fortran ordered flag */
+    const npy_intp *dims = array_dimensions(ary);
+    for (i=0; i < nd; ++i)
+      n_non_one += (dims[i] != 1) ? 1 : 0;
+    if (n_non_one > 1)
+      array_clearflags(ary,NPY_ARRAY_CARRAY);
+    array_enableflags(ary,NPY_ARRAY_FARRAY);
+    /* Recompute the strides */
+    strides[0] = strides[nd-1];
+    for (i=1; i < nd; ++i)
+      strides[i] = strides[i-1] * array_size(ary,i-1);
+    return success;
+  }
+}
+
+/* Combine all NumPy fragments into one for convenience */
+%fragment("NumPy_Fragments",
+          "header",
+          fragment="NumPy_Backward_Compatibility",
+          fragment="NumPy_Macros",
+          fragment="NumPy_Utilities",
+          fragment="NumPy_Object_to_Array",
+          fragment="NumPy_Array_Requirements")
+{
+}
+
+/* End John Hunter translation (with modifications by Bill Spotz)
+ */
+
+/* %numpy_typemaps() macro
+ *
+ * This macro defines a family of 75 typemaps that allow C arguments
+ * of the form
+ *
+ *    1. (DATA_TYPE IN_ARRAY1[ANY])
+ *    2. (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1)
+ *    3. (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1)
+ *
+ *    4. (DATA_TYPE IN_ARRAY2[ANY][ANY])
+ *    5. (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ *    6. (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2)
+ *    7. (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ *    8. (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2)
+ *
+ *    9. (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY])
+ *   10. (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+ *   11. (DATA_TYPE** IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+ *   12. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3)
+ *   13. (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+ *   14. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3)
+ *
+ *   15. (DATA_TYPE IN_ARRAY4[ANY][ANY][ANY][ANY])
+ *   16. (DATA_TYPE* IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+ *   17. (DATA_TYPE** IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+ *   18. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, , DIM_TYPE DIM4, DATA_TYPE* IN_ARRAY4)
+ *   19. (DATA_TYPE* IN_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+ *   20. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_FARRAY4)
+ *
+ *   21. (DATA_TYPE INPLACE_ARRAY1[ANY])
+ *   22. (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1)
+ *   23. (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1)
+ *
+ *   24. (DATA_TYPE INPLACE_ARRAY2[ANY][ANY])
+ *   25. (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ *   26. (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2)
+ *   27. (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ *   28. (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2)
+ *
+ *   29. (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY])
+ *   30. (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+ *   31. (DATA_TYPE** INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+ *   32. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_ARRAY3)
+ *   33. (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+ *   34. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_FARRAY3)
+ *
+ *   35. (DATA_TYPE INPLACE_ARRAY4[ANY][ANY][ANY][ANY])
+ *   36. (DATA_TYPE* INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+ *   37. (DATA_TYPE** INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+ *   38. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_ARRAY4)
+ *   39. (DATA_TYPE* INPLACE_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+ *   40. (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_FARRAY4)
+ *
+ *   41. (DATA_TYPE ARGOUT_ARRAY1[ANY])
+ *   42. (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1)
+ *   43. (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1)
+ *
+ *   44. (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY])
+ *
+ *   45. (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY])
+ *
+ *   46. (DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY])
+ *
+ *   47. (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1)
+ *   48. (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1)
+ *
+ *   49. (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ *   50. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2)
+ *   51. (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ *   52. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2)
+ *
+ *   53. (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+ *   54. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3)
+ *   55. (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+ *   56. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_FARRAY3)
+ *
+ *   57. (DATA_TYPE** ARGOUTVIEW_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ *   58. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEW_ARRAY4)
+ *   59. (DATA_TYPE** ARGOUTVIEW_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ *   60. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEW_FARRAY4)
+ *
+ *   61. (DATA_TYPE** ARGOUTVIEWM_ARRAY1, DIM_TYPE* DIM1)
+ *   62. (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEWM_ARRAY1)
+ *
+ *   63. (DATA_TYPE** ARGOUTVIEWM_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ *   64. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_ARRAY2)
+ *   65. (DATA_TYPE** ARGOUTVIEWM_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ *   66. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_FARRAY2)
+ *
+ *   67. (DATA_TYPE** ARGOUTVIEWM_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+ *   68. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEWM_ARRAY3)
+ *   69. (DATA_TYPE** ARGOUTVIEWM_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+ *   70. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEWM_FARRAY3)
+ *
+ *   71. (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ *   72. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_ARRAY4)
+ *   73. (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ *   74. (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_FARRAY4)
+ *
+ *   75. (DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)
+ *
+ * where "DATA_TYPE" is any type supported by the NumPy module, and
+ * "DIM_TYPE" is any int-like type suitable for specifying dimensions.
+ * The difference between "ARRAY" typemaps and "FARRAY" typemaps is
+ * that the "FARRAY" typemaps expect Fortran ordering of
+ * multidimensional arrays.  In python, the dimensions will not need
+ * to be specified (except for the "DATA_TYPE* ARGOUT_ARRAY1"
+ * typemaps).  The IN_ARRAYs can be a numpy array or any sequence that
+ * can be converted to a numpy array of the specified type.  The
+ * INPLACE_ARRAYs must be numpy arrays of the appropriate type.  The
+ * ARGOUT_ARRAYs will be returned as new numpy arrays of the
+ * appropriate type.
+ *
+ * These typemaps can be applied to existing functions using the
+ * %apply directive.  For example:
+ *
+ *     %apply (double* IN_ARRAY1, int DIM1) {(double* series, int length)};
+ *     double prod(double* series, int length);
+ *
+ *     %apply (int DIM1, int DIM2, double* INPLACE_ARRAY2)
+ *           {(int rows, int cols, double* matrix        )};
+ *     void floor(int rows, int cols, double* matrix, double f);
+ *
+ *     %apply (double IN_ARRAY3[ANY][ANY][ANY])
+ *           {(double tensor[2][2][2]         )};
+ *     %apply (double ARGOUT_ARRAY3[ANY][ANY][ANY])
+ *           {(double low[2][2][2]                )};
+ *     %apply (double ARGOUT_ARRAY3[ANY][ANY][ANY])
+ *           {(double upp[2][2][2]                )};
+ *     void luSplit(double tensor[2][2][2],
+ *                  double low[2][2][2],
+ *                  double upp[2][2][2]    );
+ *
+ * or directly with
+ *
+ *     double prod(double* IN_ARRAY1, int DIM1);
+ *
+ *     void floor(int DIM1, int DIM2, double* INPLACE_ARRAY2, double f);
+ *
+ *     void luSplit(double IN_ARRAY3[ANY][ANY][ANY],
+ *                  double ARGOUT_ARRAY3[ANY][ANY][ANY],
+ *                  double ARGOUT_ARRAY3[ANY][ANY][ANY]);
+ */
+
+%define %numpy_typemaps(DATA_TYPE, DATA_TYPECODE, DIM_TYPE)
+
+/************************/
+/* Input Array Typemaps */
+/************************/
+
+/* Typemap suite for (DATA_TYPE IN_ARRAY1[ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE IN_ARRAY1[ANY])
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE IN_ARRAY1[ANY])
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[1] = { $1_dim0 };
+  array = obj_to_array_contiguous_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 1) ||
+      !require_size(array, size, 1)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(freearg)
+  (DATA_TYPE IN_ARRAY1[ANY])
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[1] = { -1 };
+  array = obj_to_array_contiguous_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 1) ||
+      !require_size(array, size, 1)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[1] = {-1};
+  array = obj_to_array_contiguous_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 1) ||
+      !require_size(array, size, 1)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE IN_ARRAY2[ANY][ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE IN_ARRAY2[ANY][ANY])
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE IN_ARRAY2[ANY][ANY])
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[2] = { $1_dim0, $1_dim1 };
+  array = obj_to_array_contiguous_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 2) ||
+      !require_size(array, size, 2)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(freearg)
+  (DATA_TYPE IN_ARRAY2[ANY][ANY])
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[2] = { -1, -1 };
+  array = obj_to_array_contiguous_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 2) ||
+      !require_size(array, size, 2)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[2] = { -1, -1 };
+  array = obj_to_array_contiguous_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 2) ||
+      !require_size(array, size, 2)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[2] = { -1, -1 };
+  array = obj_to_array_fortran_allow_conversion($input,
+                                                DATA_TYPECODE,
+                                                &is_new_object);
+  if (!array || !require_dimensions(array, 2) ||
+      !require_size(array, size, 2) || !require_fortran(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[2] = { -1, -1 };
+  array = obj_to_array_fortran_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 2) ||
+      !require_size(array, size, 2) || !require_fortran(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY])
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY])
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[3] = { $1_dim0, $1_dim1, $1_dim2 };
+  array = obj_to_array_contiguous_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 3) ||
+      !require_size(array, size, 3)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(freearg)
+  (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY])
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[3] = { -1, -1, -1 };
+  array = obj_to_array_contiguous_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 3) ||
+      !require_size(array, size, 3)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE** IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE** IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  /* for now, only concerned with lists */
+  $1 = PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE** IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+  (DATA_TYPE** array=NULL, PyArrayObject** object_array=NULL, int* is_new_object_array=NULL)
+{
+  npy_intp size[2] = { -1, -1 };
+  PyArrayObject* temp_array;
+  Py_ssize_t i;
+  int is_new_object;
+
+  /* length of the list */
+  $2 = PyList_Size($input);
+
+  /* the arrays */
+  array = (DATA_TYPE **)malloc($2*sizeof(DATA_TYPE *));
+  object_array = (PyArrayObject **)calloc($2,sizeof(PyArrayObject *));
+  is_new_object_array = (int *)calloc($2,sizeof(int));
+
+  if (array == NULL || object_array == NULL || is_new_object_array == NULL)
+  {
+    SWIG_fail;
+  }
+
+  for (i=0; i<$2; i++)
+  {
+    temp_array = obj_to_array_contiguous_allow_conversion(PySequence_GetItem($input,i), DATA_TYPECODE, &is_new_object);
+
+    /* the new array must be stored so that it can be destroyed in freearg */
+    object_array[i] = temp_array;
+    is_new_object_array[i] = is_new_object;
+
+    if (!temp_array || !require_dimensions(temp_array, 2)) SWIG_fail;
+
+    /* store the size of the first array in the list, then use that for comparison. */
+    if (i == 0)
+    {
+      size[0] = array_size(temp_array,0);
+      size[1] = array_size(temp_array,1);
+    }
+
+    if (!require_size(temp_array, size, 2)) SWIG_fail;
+
+    array[i] = (DATA_TYPE*) array_data(temp_array);
+  }
+
+  $1 = (DATA_TYPE**) array;
+  $3 = (DIM_TYPE) size[0];
+  $4 = (DIM_TYPE) size[1];
+}
+%typemap(freearg)
+  (DATA_TYPE** IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  Py_ssize_t i;
+
+  if (array$argnum!=NULL) free(array$argnum);
+
+  /*freeing the individual arrays if needed */
+  if (object_array$argnum!=NULL)
+  {
+    if (is_new_object_array$argnum!=NULL)
+    {
+      for (i=0; i<$2; i++)
+      {
+        if (object_array$argnum[i] != NULL && is_new_object_array$argnum[i])
+        { Py_DECREF(object_array$argnum[i]); }
+      }
+      free(is_new_object_array$argnum);
+    }
+    free(object_array$argnum);
+  }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3,
+ *                    DATA_TYPE* IN_ARRAY3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[3] = { -1, -1, -1 };
+  array = obj_to_array_contiguous_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 3) ||
+      !require_size(array, size, 3)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[3] = { -1, -1, -1 };
+  array = obj_to_array_fortran_allow_conversion($input, DATA_TYPECODE,
+                                                &is_new_object);
+  if (!array || !require_dimensions(array, 3) ||
+      !require_size(array, size, 3) | !require_fortran(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3,
+ *                    DATA_TYPE* IN_FARRAY3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[3] = { -1, -1, -1 };
+  array = obj_to_array_fortran_allow_conversion($input,
+                                                   DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 3) ||
+      !require_size(array, size, 3) || !require_fortran(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE IN_ARRAY4[ANY][ANY][ANY][ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE IN_ARRAY4[ANY][ANY][ANY][ANY])
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE IN_ARRAY4[ANY][ANY][ANY][ANY])
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[4] = { $1_dim0, $1_dim1, $1_dim2 , $1_dim3};
+  array = obj_to_array_contiguous_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 4) ||
+      !require_size(array, size, 4)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(freearg)
+  (DATA_TYPE IN_ARRAY4[ANY][ANY][ANY][ANY])
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3, DIM_TYPE DIM4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[4] = { -1, -1, -1, -1 };
+  array = obj_to_array_contiguous_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 4) ||
+      !require_size(array, size, 4)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+  $5 = (DIM_TYPE) array_size(array,3);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE** IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3, DIM_TYPE DIM4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE** IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  /* for now, only concerned with lists */
+  $1 = PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE** IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+  (DATA_TYPE** array=NULL, PyArrayObject** object_array=NULL, int* is_new_object_array=NULL)
+{
+  npy_intp size[3] = { -1, -1, -1 };
+  PyArrayObject* temp_array;
+  Py_ssize_t i;
+  int is_new_object;
+
+  /* length of the list */
+  $2 = PyList_Size($input);
+
+  /* the arrays */
+  array = (DATA_TYPE **)malloc($2*sizeof(DATA_TYPE *));
+  object_array = (PyArrayObject **)calloc($2,sizeof(PyArrayObject *));
+  is_new_object_array = (int *)calloc($2,sizeof(int));
+
+  if (array == NULL || object_array == NULL || is_new_object_array == NULL)
+  {
+    SWIG_fail;
+  }
+
+  for (i=0; i<$2; i++)
+  {
+    temp_array = obj_to_array_contiguous_allow_conversion(PySequence_GetItem($input,i), DATA_TYPECODE, &is_new_object);
+
+    /* the new array must be stored so that it can be destroyed in freearg */
+    object_array[i] = temp_array;
+    is_new_object_array[i] = is_new_object;
+
+    if (!temp_array || !require_dimensions(temp_array, 3)) SWIG_fail;
+
+    /* store the size of the first array in the list, then use that for comparison. */
+    if (i == 0)
+    {
+      size[0] = array_size(temp_array,0);
+      size[1] = array_size(temp_array,1);
+      size[2] = array_size(temp_array,2);
+    }
+
+    if (!require_size(temp_array, size, 3)) SWIG_fail;
+
+    array[i] = (DATA_TYPE*) array_data(temp_array);
+  }
+
+  $1 = (DATA_TYPE**) array;
+  $3 = (DIM_TYPE) size[0];
+  $4 = (DIM_TYPE) size[1];
+  $5 = (DIM_TYPE) size[2];
+}
+%typemap(freearg)
+  (DATA_TYPE** IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  Py_ssize_t i;
+
+  if (array$argnum!=NULL) free(array$argnum);
+
+  /*freeing the individual arrays if needed */
+  if (object_array$argnum!=NULL)
+  {
+    if (is_new_object_array$argnum!=NULL)
+    {
+      for (i=0; i<$2; i++)
+      {
+        if (object_array$argnum[i] != NULL && is_new_object_array$argnum[i])
+        { Py_DECREF(object_array$argnum[i]); }
+      }
+      free(is_new_object_array$argnum);
+    }
+    free(object_array$argnum);
+  }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4,
+ *                    DATA_TYPE* IN_ARRAY4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_ARRAY4)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_ARRAY4)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[4] = { -1, -1, -1 , -1};
+  array = obj_to_array_contiguous_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 4) ||
+      !require_size(array, size, 4)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DIM_TYPE) array_size(array,3);
+  $5 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_ARRAY4)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DATA_TYPE* IN_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3, DIM_TYPE DIM4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* IN_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* IN_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[4] = { -1, -1, -1, -1 };
+  array = obj_to_array_fortran_allow_conversion($input, DATA_TYPECODE,
+                                                &is_new_object);
+  if (!array || !require_dimensions(array, 4) ||
+      !require_size(array, size, 4) | !require_fortran(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+  $5 = (DIM_TYPE) array_size(array,3);
+}
+%typemap(freearg)
+  (DATA_TYPE* IN_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4,
+ *                    DATA_TYPE* IN_FARRAY4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_FARRAY4)
+{
+  $1 = is_array($input) || PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_FARRAY4)
+  (PyArrayObject* array=NULL, int is_new_object=0)
+{
+  npy_intp size[4] = { -1, -1, -1 , -1 };
+  array = obj_to_array_fortran_allow_conversion($input, DATA_TYPECODE,
+                                                   &is_new_object);
+  if (!array || !require_dimensions(array, 4) ||
+      !require_size(array, size, 4) || !require_fortran(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DIM_TYPE) array_size(array,3);
+  $5 = (DATA_TYPE*) array_data(array);
+}
+%typemap(freearg)
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_FARRAY4)
+{
+  if (is_new_object$argnum && array$argnum)
+    { Py_DECREF(array$argnum); }
+}
+
+/***************************/
+/* In-Place Array Typemaps */
+/***************************/
+
+/* Typemap suite for (DATA_TYPE INPLACE_ARRAY1[ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE INPLACE_ARRAY1[ANY])
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE INPLACE_ARRAY1[ANY])
+  (PyArrayObject* array=NULL)
+{
+  npy_intp size[1] = { $1_dim0 };
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,1) || !require_size(array, size, 1) ||
+      !require_contiguous(array) || !require_native(array)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1)
+  (PyArrayObject* array=NULL, int i=1)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,1) || !require_contiguous(array)
+      || !require_native(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = 1;
+  for (i=0; i < array_numdims(array); ++i) $2 *= array_size(array,i);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1)
+  (PyArrayObject* array=NULL, int i=0)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,1) || !require_contiguous(array)
+      || !require_native(array)) SWIG_fail;
+  $1 = 1;
+  for (i=0; i < array_numdims(array); ++i) $1 *= array_size(array,i);
+  $2 = (DATA_TYPE*) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE INPLACE_ARRAY2[ANY][ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE INPLACE_ARRAY2[ANY][ANY])
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE INPLACE_ARRAY2[ANY][ANY])
+  (PyArrayObject* array=NULL)
+{
+  npy_intp size[2] = { $1_dim0, $1_dim1 };
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,2) || !require_size(array, size, 2) ||
+      !require_contiguous(array) || !require_native(array)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,2) || !require_contiguous(array)
+      || !require_native(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,2) || !require_contiguous(array) ||
+      !require_native(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DATA_TYPE*) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,2) || !require_contiguous(array)
+      || !require_native(array) || !require_fortran(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,2) || !require_contiguous(array) ||
+      !require_native(array) || !require_fortran(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DATA_TYPE*) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY])
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY])
+  (PyArrayObject* array=NULL)
+{
+  npy_intp size[3] = { $1_dim0, $1_dim1, $1_dim2 };
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,3) || !require_size(array, size, 3) ||
+      !require_contiguous(array) || !require_native(array)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,3) || !require_contiguous(array) ||
+      !require_native(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+}
+
+/* Typemap suite for (DATA_TYPE** INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE** INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  $1 = PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE** INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+  (DATA_TYPE** array=NULL, PyArrayObject** object_array=NULL)
+{
+  npy_intp size[2] = { -1, -1 };
+  PyArrayObject* temp_array;
+  Py_ssize_t i;
+
+  /* length of the list */
+  $2 = PyList_Size($input);
+
+  /* the arrays */
+  array = (DATA_TYPE **)malloc($2*sizeof(DATA_TYPE *));
+  object_array = (PyArrayObject **)calloc($2,sizeof(PyArrayObject *));
+
+  if (array == NULL || object_array == NULL)
+  {
+    SWIG_fail;
+  }
+
+  for (i=0; i<$2; i++)
+  {
+    temp_array = obj_to_array_no_conversion(PySequence_GetItem($input,i), DATA_TYPECODE);
+
+    /* the new array must be stored so that it can be destroyed in freearg */
+    object_array[i] = temp_array;
+
+    if ( !temp_array || !require_dimensions(temp_array, 2) ||
+      !require_contiguous(temp_array) ||
+      !require_native(temp_array) ||
+      !PyArray_EquivTypenums(array_type(temp_array), DATA_TYPECODE)
+    ) SWIG_fail;
+
+    /* store the size of the first array in the list, then use that for comparison. */
+    if (i == 0)
+    {
+      size[0] = array_size(temp_array,0);
+      size[1] = array_size(temp_array,1);
+    }
+
+    if (!require_size(temp_array, size, 2)) SWIG_fail;
+
+    array[i] = (DATA_TYPE*) array_data(temp_array);
+  }
+
+  $1 = (DATA_TYPE**) array;
+  $3 = (DIM_TYPE) size[0];
+  $4 = (DIM_TYPE) size[1];
+}
+%typemap(freearg)
+  (DATA_TYPE** INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  if (array$argnum!=NULL) free(array$argnum);
+  if (object_array$argnum!=NULL) free(object_array$argnum);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3,
+ *                    DATA_TYPE* INPLACE_ARRAY3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_ARRAY3)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_ARRAY3)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,3) || !require_contiguous(array)
+      || !require_native(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DATA_TYPE*) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,3) || !require_contiguous(array) ||
+      !require_native(array) || !require_fortran(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3,
+ *                    DATA_TYPE* INPLACE_FARRAY3)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_FARRAY3)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_FARRAY3)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,3) || !require_contiguous(array)
+      || !require_native(array) || !require_fortran(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DATA_TYPE*) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE INPLACE_ARRAY4[ANY][ANY][ANY][ANY])
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE INPLACE_ARRAY4[ANY][ANY][ANY][ANY])
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE INPLACE_ARRAY4[ANY][ANY][ANY][ANY])
+  (PyArrayObject* array=NULL)
+{
+  npy_intp size[4] = { $1_dim0, $1_dim1, $1_dim2 , $1_dim3 };
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,4) || !require_size(array, size, 4) ||
+      !require_contiguous(array) || !require_native(array)) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3, DIM_TYPE DIM4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,4) || !require_contiguous(array) ||
+      !require_native(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+  $5 = (DIM_TYPE) array_size(array,3);
+}
+
+/* Typemap suite for (DATA_TYPE** INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3, DIM_TYPE DIM4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE** INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  $1 = PySequence_Check($input);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE** INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+  (DATA_TYPE** array=NULL, PyArrayObject** object_array=NULL)
+{
+  npy_intp size[3] = { -1, -1, -1 };
+  PyArrayObject* temp_array;
+  Py_ssize_t i;
+
+  /* length of the list */
+  $2 = PyList_Size($input);
+
+  /* the arrays */
+  array = (DATA_TYPE **)malloc($2*sizeof(DATA_TYPE *));
+  object_array = (PyArrayObject **)calloc($2,sizeof(PyArrayObject *));
+
+  if (array == NULL || object_array == NULL)
+  {
+    SWIG_fail;
+  }
+
+  for (i=0; i<$2; i++)
+  {
+    temp_array = obj_to_array_no_conversion(PySequence_GetItem($input,i), DATA_TYPECODE);
+
+    /* the new array must be stored so that it can be destroyed in freearg */
+    object_array[i] = temp_array;
+
+    if ( !temp_array || !require_dimensions(temp_array, 3) ||
+      !require_contiguous(temp_array) ||
+      !require_native(temp_array) ||
+      !PyArray_EquivTypenums(array_type(temp_array), DATA_TYPECODE)
+    ) SWIG_fail;
+
+    /* store the size of the first array in the list, then use that for comparison. */
+    if (i == 0)
+    {
+      size[0] = array_size(temp_array,0);
+      size[1] = array_size(temp_array,1);
+      size[2] = array_size(temp_array,2);
+    }
+
+    if (!require_size(temp_array, size, 3)) SWIG_fail;
+
+    array[i] = (DATA_TYPE*) array_data(temp_array);
+  }
+
+  $1 = (DATA_TYPE**) array;
+  $3 = (DIM_TYPE) size[0];
+  $4 = (DIM_TYPE) size[1];
+  $5 = (DIM_TYPE) size[2];
+}
+%typemap(freearg)
+  (DATA_TYPE** INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  if (array$argnum!=NULL) free(array$argnum);
+  if (object_array$argnum!=NULL) free(object_array$argnum);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4,
+ *                    DATA_TYPE* INPLACE_ARRAY4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_ARRAY4)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_ARRAY4)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,4) || !require_contiguous(array)
+      || !require_native(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DIM_TYPE) array_size(array,3);
+  $5 = (DATA_TYPE*) array_data(array);
+}
+
+/* Typemap suite for (DATA_TYPE* INPLACE_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2,
+ *                    DIM_TYPE DIM3, DIM_TYPE DIM4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,4) || !require_contiguous(array) ||
+      !require_native(array) || !require_fortran(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = (DIM_TYPE) array_size(array,0);
+  $3 = (DIM_TYPE) array_size(array,1);
+  $4 = (DIM_TYPE) array_size(array,2);
+  $5 = (DIM_TYPE) array_size(array,3);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3,
+ *                    DATA_TYPE* INPLACE_FARRAY4)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_FARRAY4)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_FARRAY4)
+  (PyArrayObject* array=NULL)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_dimensions(array,4) || !require_contiguous(array)
+      || !require_native(array) || !require_fortran(array)) SWIG_fail;
+  $1 = (DIM_TYPE) array_size(array,0);
+  $2 = (DIM_TYPE) array_size(array,1);
+  $3 = (DIM_TYPE) array_size(array,2);
+  $4 = (DIM_TYPE) array_size(array,3);
+  $5 = (DATA_TYPE*) array_data(array);
+}
+
+/*************************/
+/* Argout Array Typemaps */
+/*************************/
+
+/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY1[ANY])
+ */
+%typemap(in,numinputs=0,
+         fragment="NumPy_Backward_Compatibility,NumPy_Macros")
+  (DATA_TYPE ARGOUT_ARRAY1[ANY])
+  (PyObject* array = NULL)
+{
+  npy_intp dims[1] = { $1_dim0 };
+  array = PyArray_SimpleNew(1, dims, DATA_TYPECODE);
+  if (!array) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(argout)
+  (DATA_TYPE ARGOUT_ARRAY1[ANY])
+{
+  $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum);
+}
+
+/* Typemap suite for (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1)
+ */
+%typemap(in,numinputs=1,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1)
+  (PyObject* array = NULL)
+{
+  npy_intp dims[1];
+  if (!PyLong_Check($input))
+  {
+    const char* typestring = pytype_string($input);
+    PyErr_Format(PyExc_TypeError,
+                 "Int dimension expected.  '%s' given.",
+                 typestring);
+    SWIG_fail;
+  }
+  $2 = (DIM_TYPE) PyLong_AsSsize_t($input);
+  if ($2 == -1 && PyErr_Occurred()) SWIG_fail;
+  dims[0] = (npy_intp) $2;
+  array = PyArray_SimpleNew(1, dims, DATA_TYPECODE);
+  if (!array) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+}
+%typemap(argout)
+  (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1)
+{
+  $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum);
+}
+
+/* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1)
+ */
+%typemap(in,numinputs=1,
+         fragment="NumPy_Fragments")
+  (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1)
+  (PyObject* array = NULL)
+{
+  npy_intp dims[1];
+  if (!PyLong_Check($input))
+  {
+    const char* typestring = pytype_string($input);
+    PyErr_Format(PyExc_TypeError,
+                 "Int dimension expected.  '%s' given.",
+                 typestring);
+    SWIG_fail;
+  }
+  $1 = (DIM_TYPE) PyLong_AsSsize_t($input);
+  if ($1 == -1 && PyErr_Occurred()) SWIG_fail;
+  dims[0] = (npy_intp) $1;
+  array = PyArray_SimpleNew(1, dims, DATA_TYPECODE);
+  if (!array) SWIG_fail;
+  $2 = (DATA_TYPE*) array_data(array);
+}
+%typemap(argout)
+  (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1)
+{
+  $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum);
+}
+
+/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY])
+ */
+%typemap(in,numinputs=0,
+         fragment="NumPy_Backward_Compatibility,NumPy_Macros")
+  (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY])
+  (PyObject* array = NULL)
+{
+  npy_intp dims[2] = { $1_dim0, $1_dim1 };
+  array = PyArray_SimpleNew(2, dims, DATA_TYPECODE);
+  if (!array) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(argout)
+  (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY])
+{
+  $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum);
+}
+
+/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY])
+ */
+%typemap(in,numinputs=0,
+         fragment="NumPy_Backward_Compatibility,NumPy_Macros")
+  (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY])
+  (PyObject* array = NULL)
+{
+  npy_intp dims[3] = { $1_dim0, $1_dim1, $1_dim2 };
+  array = PyArray_SimpleNew(3, dims, DATA_TYPECODE);
+  if (!array) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(argout)
+  (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY])
+{
+  $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum);
+}
+
+/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY])
+ */
+%typemap(in,numinputs=0,
+         fragment="NumPy_Backward_Compatibility,NumPy_Macros")
+  (DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY])
+  (PyObject* array = NULL)
+{
+  npy_intp dims[4] = { $1_dim0, $1_dim1, $1_dim2, $1_dim3 };
+  array = PyArray_SimpleNew(4, dims, DATA_TYPECODE);
+  if (!array) SWIG_fail;
+  $1 = ($1_ltype) array_data(array);
+}
+%typemap(argout)
+  (DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY])
+{
+  $result = SWIG_Python_AppendOutput($result,(PyObject*)array$argnum);
+}
+
+/*****************************/
+/* Argoutview Array Typemaps */
+/*****************************/
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1    )
+  (DATA_TYPE*  data_temp = NULL , DIM_TYPE  dim_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1)
+{
+  npy_intp dims[1] = { *$2 };
+  PyObject* obj = PyArray_SimpleNewFromData(1, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DATA_TYPE** ARGOUTVIEW_ARRAY1)
+  (DIM_TYPE  dim_temp, DATA_TYPE*  data_temp = NULL )
+{
+  $1 = &dim_temp;
+  $2 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1)
+{
+  npy_intp dims[1] = { *$1 };
+  PyObject* obj = PyArray_SimpleNewFromData(1, dims, DATA_TYPECODE, (void*)(*$2));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1     , DIM_TYPE* DIM2     )
+  (DATA_TYPE*  data_temp = NULL , DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+{
+  npy_intp dims[2] = { *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1     , DIM_TYPE* DIM2     , DATA_TYPE** ARGOUTVIEW_ARRAY2)
+  (DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp, DATA_TYPE*  data_temp = NULL )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2)
+{
+  npy_intp dims[2] = { *$1, *$2 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$3));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1     , DIM_TYPE* DIM2     )
+  (DATA_TYPE*  data_temp = NULL  , DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements")
+  (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+{
+  npy_intp dims[2] = { *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1     , DIM_TYPE* DIM2     , DATA_TYPE** ARGOUTVIEW_FARRAY2)
+  (DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp, DATA_TYPE*  data_temp = NULL  )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2)
+{
+  npy_intp dims[2] = { *$1, *$2 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$3));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    )
+  (DATA_TYPE* data_temp = NULL  , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+{
+  npy_intp dims[3] = { *$2, *$3, *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3,
+                      DATA_TYPE** ARGOUTVIEW_ARRAY3)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DATA_TYPE* data_temp = NULL)
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3)
+{
+  npy_intp dims[3] = { *$1, *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$4));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    )
+  (DATA_TYPE* data_temp = NULL   , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements")
+  (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+{
+  npy_intp dims[3] = { *$2, *$3, *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3,
+                      DATA_TYPE** ARGOUTVIEW_FARRAY3)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DATA_TYPE** ARGOUTVIEW_FARRAY3)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DATA_TYPE* data_temp = NULL   )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_FARRAY3)
+{
+  npy_intp dims[3] = { *$1, *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$4));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_ARRAY4, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    )
+  (DATA_TYPE* data_temp = NULL  , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+  $5 = &dim4_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DATA_TYPE** ARGOUTVIEW_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+{
+  npy_intp dims[4] = { *$2, *$3, *$4 , *$5 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4,
+                      DATA_TYPE** ARGOUTVIEW_ARRAY4)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    , DATA_TYPE** ARGOUTVIEW_ARRAY4)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp, DATA_TYPE* data_temp = NULL  )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &dim4_temp;
+  $5 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEW_ARRAY4)
+{
+  npy_intp dims[4] = { *$1, *$2, *$3 , *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$5));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEW_FARRAY4, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    )
+  (DATA_TYPE* data_temp = NULL   , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+  $5 = &dim4_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements")
+  (DATA_TYPE** ARGOUTVIEW_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+{
+  npy_intp dims[4] = { *$2, *$3, *$4 , *$5 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4,
+                      DATA_TYPE** ARGOUTVIEW_FARRAY4)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    , DATA_TYPE** ARGOUTVIEW_FARRAY4)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp, DATA_TYPE* data_temp = NULL   )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &dim4_temp;
+  $5 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEW_FARRAY4)
+{
+  npy_intp dims[4] = { *$1, *$2, *$3 , *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$5));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/*************************************/
+/* Managed Argoutview Array Typemaps */
+/*************************************/
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY1, DIM_TYPE* DIM1)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY1, DIM_TYPE* DIM1    )
+  (DATA_TYPE*  data_temp = NULL  , DIM_TYPE  dim_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY1, DIM_TYPE* DIM1)
+{
+  npy_intp dims[1] = { *$2 };
+  PyObject* obj = PyArray_SimpleNewFromData(1, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEWM_ARRAY1)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DATA_TYPE** ARGOUTVIEWM_ARRAY1)
+  (DIM_TYPE  dim_temp, DATA_TYPE*  data_temp = NULL  )
+{
+  $1 = &dim_temp;
+  $2 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEWM_ARRAY1)
+{
+  npy_intp dims[1] = { *$1 };
+  PyObject* obj = PyArray_SimpleNewFromData(1, dims, DATA_TYPECODE, (void*)(*$2));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$2), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$2), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY2, DIM_TYPE* DIM1     , DIM_TYPE* DIM2     )
+  (DATA_TYPE*  data_temp = NULL  , DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+{
+  npy_intp dims[2] = { *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_ARRAY2)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1     , DIM_TYPE* DIM2     , DATA_TYPE** ARGOUTVIEWM_ARRAY2)
+  (DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp, DATA_TYPE*  data_temp = NULL  )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_ARRAY2)
+{
+  npy_intp dims[2] = { *$1, *$2 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$3));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$3), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$3), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_FARRAY2, DIM_TYPE* DIM1     , DIM_TYPE* DIM2     )
+  (DATA_TYPE*  data_temp = NULL   , DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
+{
+  npy_intp dims[2] = { *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_FARRAY2)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1     , DIM_TYPE* DIM2     , DATA_TYPE** ARGOUTVIEWM_FARRAY2)
+  (DIM_TYPE  dim1_temp, DIM_TYPE  dim2_temp, DATA_TYPE*  data_temp = NULL   )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_FARRAY2)
+{
+  npy_intp dims[2] = { *$1, *$2 };
+  PyObject* obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$3));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$3), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$3), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY3, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    )
+  (DATA_TYPE* data_temp = NULL   , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+{
+  npy_intp dims[3] = { *$2, *$3, *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3,
+                      DATA_TYPE** ARGOUTVIEWM_ARRAY3)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DATA_TYPE** ARGOUTVIEWM_ARRAY3)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DATA_TYPE* data_temp = NULL   )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEWM_ARRAY3)
+{
+  npy_intp dims[3] = { *$1, *$2, *$3 };
+  PyObject* obj= PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$4));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$4), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$4), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_FARRAY3, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    )
+  (DATA_TYPE* data_temp = NULL    , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
+{
+  npy_intp dims[3] = { *$2, *$3, *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3,
+                      DATA_TYPE** ARGOUTVIEWM_FARRAY3)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DATA_TYPE** ARGOUTVIEWM_FARRAY3)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DATA_TYPE* data_temp = NULL    )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEWM_FARRAY3)
+{
+  npy_intp dims[3] = { *$1, *$2, *$3 };
+  PyObject* obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$4));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$4), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$4), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    )
+  (DATA_TYPE* data_temp = NULL   , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+  $5 = &dim4_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+{
+  npy_intp dims[4] = { *$2, *$3, *$4 , *$5 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4,
+                      DATA_TYPE** ARGOUTVIEWM_ARRAY4)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    , DATA_TYPE** ARGOUTVIEWM_ARRAY4)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp, DATA_TYPE* data_temp = NULL   )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &dim4_temp;
+  $5 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_ARRAY4)
+{
+  npy_intp dims[4] = { *$1, *$2, *$3 , *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$5));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$5), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$5), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2,
+                      DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+ */
+%typemap(in,numinputs=0)
+  (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    )
+  (DATA_TYPE* data_temp = NULL    , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp)
+{
+  $1 = &data_temp;
+  $2 = &dim1_temp;
+  $3 = &dim2_temp;
+  $4 = &dim3_temp;
+  $5 = &dim4_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities")
+  (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
+{
+  npy_intp dims[4] = { *$2, *$3, *$4 , *$5 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$1));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$1), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$1), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4,
+                      DATA_TYPE** ARGOUTVIEWM_FARRAY4)
+ */
+%typemap(in,numinputs=0)
+  (DIM_TYPE* DIM1    , DIM_TYPE* DIM2    , DIM_TYPE* DIM3    , DIM_TYPE* DIM4    , DATA_TYPE** ARGOUTVIEWM_FARRAY4)
+  (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DIM_TYPE dim4_temp, DATA_TYPE* data_temp = NULL    )
+{
+  $1 = &dim1_temp;
+  $2 = &dim2_temp;
+  $3 = &dim3_temp;
+  $4 = &dim4_temp;
+  $5 = &data_temp;
+}
+%typemap(argout,
+         fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements,NumPy_Utilities")
+  (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_FARRAY4)
+{
+  npy_intp dims[4] = { *$1, *$2, *$3 , *$4 };
+  PyObject* obj = PyArray_SimpleNewFromData(4, dims, DATA_TYPECODE, (void*)(*$5));
+  PyArrayObject* array = (PyArrayObject*) obj;
+
+  if (!array || !require_fortran(array)) SWIG_fail;
+
+%#ifdef SWIGPY_USE_CAPSULE
+    PyObject* cap = PyCapsule_New((void*)(*$5), SWIGPY_CAPSULE_NAME, free_cap);
+%#else
+    PyObject* cap = PyCObject_FromVoidPtr((void*)(*$5), free);
+%#endif
+
+%#if NPY_API_VERSION < 0x00000007
+  PyArray_BASE(array) = cap;
+%#else
+  PyArray_SetBaseObject(array,cap);
+%#endif
+
+  $result = SWIG_Python_AppendOutput($result,obj);
+}
+
+/**************************************/
+/* In-Place Array Typemap - flattened */
+/**************************************/
+
+/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)
+ */
+%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY,
+           fragment="NumPy_Macros")
+  (DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)
+{
+  $1 = is_array($input) && PyArray_EquivTypenums(array_type($input),
+                                                 DATA_TYPECODE);
+}
+%typemap(in,
+         fragment="NumPy_Fragments")
+  (DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)
+  (PyArrayObject* array=NULL, int i=1)
+{
+  array = obj_to_array_no_conversion($input, DATA_TYPECODE);
+  if (!array || !require_c_or_f_contiguous(array)
+      || !require_native(array)) SWIG_fail;
+  $1 = (DATA_TYPE*) array_data(array);
+  $2 = 1;
+  for (i=0; i < array_numdims(array); ++i) $2 *= array_size(array,i);
+}
+
+%enddef    /* %numpy_typemaps() macro */
+/* *************************************************************** */
+
+/* Concrete instances of the %numpy_typemaps() macro: Each invocation
+ * below applies all of the typemaps above to the specified data type.
+ */
+%numpy_typemaps(signed char       , NPY_BYTE     , int)
+%numpy_typemaps(unsigned char     , NPY_UBYTE    , int)
+%numpy_typemaps(short             , NPY_SHORT    , int)
+%numpy_typemaps(unsigned short    , NPY_USHORT   , int)
+%numpy_typemaps(int               , NPY_INT      , int)
+%numpy_typemaps(unsigned int      , NPY_UINT     , int)
+%numpy_typemaps(long              , NPY_LONG     , int)
+%numpy_typemaps(unsigned long     , NPY_ULONG    , int)
+%numpy_typemaps(long long         , NPY_LONGLONG , int)
+%numpy_typemaps(unsigned long long, NPY_ULONGLONG, int)
+%numpy_typemaps(float             , NPY_FLOAT    , int)
+%numpy_typemaps(double            , NPY_DOUBLE   , int)
+%numpy_typemaps(int8_t            , NPY_INT8     , int)
+%numpy_typemaps(int16_t           , NPY_INT16    , int)
+%numpy_typemaps(int32_t           , NPY_INT32    , int)
+%numpy_typemaps(int64_t           , NPY_INT64    , int)
+%numpy_typemaps(uint8_t           , NPY_UINT8    , int)
+%numpy_typemaps(uint16_t          , NPY_UINT16   , int)
+%numpy_typemaps(uint32_t          , NPY_UINT32   , int)
+%numpy_typemaps(uint64_t          , NPY_UINT64   , int)
+
+
+/* ***************************************************************
+ * The follow macro expansion does not work, because C++ bool is 4
+ * bytes and NPY_BOOL is 1 byte
+ *
+ *    %numpy_typemaps(bool, NPY_BOOL, int)
+ */
+
+/* ***************************************************************
+ * On my Mac, I get the following warning for this macro expansion:
+ * 'swig/python detected a memory leak of type 'long double *', no destructor found.'
+ *
+ *    %numpy_typemaps(long double, NPY_LONGDOUBLE, int)
+ */
+
+#ifdef __cplusplus
+
+%include <std_complex.i>
+
+%numpy_typemaps(std::complex<float>,  NPY_CFLOAT , int)
+%numpy_typemaps(std::complex<double>, NPY_CDOUBLE, int)
+
+#endif
+
+#endif /* SWIGPYTHON */
diff --git a/src/pytrexio.i b/src/pytrexio.i
new file mode 100644
index 00000000..8d3c1748
--- /dev/null
+++ b/src/pytrexio.i
@@ -0,0 +1,126 @@
+%module pytrexio
+/* Define SWIGWORDSIZE in order to properly align long integers on 64-bit system */
+#define SWIGWORDSIZE64
+%{
+#define SWIG_FILE_WITH_INIT
+/* Include the headers in the wrapper code */
+#include "trexio_s.h"
+#include "trexio.h"
+%}
+
+/* Include stdint to recognize types from stdint.h */
+%include <stdint.i>
+
+/* NOTE:
+   carrays was useful before numpy.i was introduced. 
+   For Python interface it's better to use numpy arrays instead of carrays, because the latter are less python-ic.
+   On the other hand, carrays might be more portable to other target languages.
+// Include carrays to work with C pointers to arrays 
+%include "carrays.i"
+// Include classes that correspond to integer and float arrays
+%array_class(double, doubleArray);
+%array_class(float, floatArray);
+%array_class(int32_t, int32Array);
+%array_class(int64_t, int64Array);
+*/
+
+/* Include typemaps to play with input/output re-casting 
+   Useful when working with C pointers
+*/
+%include typemaps.i
+/* Redefine the int32_t* and int64_t* num to be output 
+   Useful for TREXIO read_num functions where the 
+   num variable is modified by address
+*/
+%apply int *OUTPUT { int32_t* const num};
+%apply int *OUTPUT { int64_t* const num};
+
+/* Does not work for arrays (SIGSEGV) */
+
+/* This enables access to trexio_[...]_read_dset_str_low set of functions
+   in order to return one long string with TREXIO_DELIM delimeter as 2-nd argument of output tuple
+   */
+%include <cstring.i>
+/* This enables read of long strings with TREXIO_DELIM delimeters that can be further converted into an array of string */
+%cstring_bounded_output(char* dset_out, 4096);
+/* This enables read of single string attributes with pre-defined max_str_len 
+   for Python we pre-define max_str_len = PYTREXIO_MAX_STR_LENGTH everywhere for simplicity
+*/
+%cstring_output_maxsize(char* const str_out, const int32_t max_str_len);
+
+/* [WIP] TREXIO back ends and exit codes can be redefined in the SWIG target language 
+   using %ignore and further #define statements (instead of disabling the type cast in the trexio.h file)
+*/
+/*
+%ignore TREXIO_HDF5;       // Ignore a macro in the header file
+%ignore TREXIO_TEST;       // Ignore a macro in the header file
+#define TREXIO_HDF5 0
+#define TREXIO_TEXT 0
+*/
+
+/* This is an attempt to make SWIG treat double * dset_out|_in, int64_t dim_out|_in pattern 
+   as a special case in order to return the NumPy array to Python from C pointer to array
+   provided by trexio_read_safe_[dset_num] function.
+   NOTE: numpy.i is currently not part of SWIG but included in the numpy distribution (under numpy/tools/swig/numpy.i)
+         This means that the interface file have to be provided to SWIG during compilation either by 
+         copying it to the local working directory or by providing -l/path/to/numpy.i flag upon SWIG compilation
+*/
+%include "numpy.i"
+
+%init %{
+import_array();
+%}
+
+/* Typemaps below change the type of numpy array dimensions from int to int64_t */
+%numpy_typemaps(double, NPY_DOUBLE, int64_t)
+%numpy_typemaps(float, NPY_FLOAT, int64_t)
+%numpy_typemaps(int32_t, NPY_INT32, int64_t)
+%numpy_typemaps(int64_t, NPY_INT64, int64_t)
+/* Enable write|read_safe functions to convert numpy arrays from/to double arrays */
+%apply (double* ARGOUT_ARRAY1, int64_t DIM1) {(double * const dset_out, const int64_t dim_out)};
+%apply (double* IN_ARRAY1, int64_t DIM1) {(const double * dset_in, const int64_t dim_in)};
+/* Enable write|read_safe functions to convert numpy arrays from/to float arrays */
+%apply (float* ARGOUT_ARRAY1, int64_t DIM1) {(float * const dset_out, const int64_t dim_out)};
+%apply (float* IN_ARRAY1, int64_t DIM1) {(const float * dset_in, const int64_t dim_in)};
+/* Enable write|read_safe functions to convert numpy arrays from/to int32 arrays */
+%apply (int32_t* ARGOUT_ARRAY1, int64_t DIM1) {(int32_t * const dset_out, const int64_t dim_out)};
+%apply (int32_t* IN_ARRAY1, int64_t DIM1) {(const int32_t * dset_in, const int64_t dim_in)};
+/* Enable write|read_safe functions to convert numpy arrays from/to int64 arrays */
+%apply (int64_t* ARGOUT_ARRAY1, int64_t DIM1) {(int64_t * const dset_out, const int64_t dim_out)};
+%apply (int64_t* IN_ARRAY1, int64_t DIM1) {(const int64_t * dset_in, const int64_t dim_in)};
+
+/* This tells SWIG to treat char ** dset_in pattern as a special case 
+   Enables access to trexio_[...]_write_dset_str set of functions directly, i.e.
+   by converting input list of strings from Python into char ** of C
+*/
+%typemap(in) char ** dset_in {
+  /* Check if is a list */
+  if (PyList_Check($input)) {
+    int size = PyList_Size($input);
+    Py_ssize_t i = 0;
+    $1 = (char **) malloc((size+1)*sizeof(char *));
+    for (i = 0; i < size; i++) {
+      PyObject *o = PyList_GetItem($input, i);
+      if (PyUnicode_Check(o)) {
+        $1[i] = PyUnicode_AsUTF8(PyList_GetItem($input,i));
+      } else {
+        PyErr_Format(PyExc_TypeError, "list must contain strings. %d/%d element was not string.", i, size);
+        free($1);
+        return NULL;
+      }
+    }
+    $1[i] = 0;
+  } else {
+    PyErr_SetString(PyExc_TypeError, "not a list");
+    return NULL;
+  }
+}
+/* This cleans up the char ** array we malloc-ed before */
+%typemap(freearg) char ** dset_in {
+  free((char *) $1);
+}
+
+/* Parse the header files to generate wrappers */
+%include "trexio_s.h"
+%include "trexio.h"
+
diff --git a/src/templates_front/build.sh b/src/templates_front/build.sh
index 61cde7bd..b0b0ddba 100644
--- a/src/templates_front/build.sh
+++ b/src/templates_front/build.sh
@@ -10,6 +10,7 @@ echo "" >> trexio.h
 
 cat prefix_s_front.h > trexio_s.h
 cat prefix_fortran.f90   > trexio_f.f90
+cat prefix_python.py   > trexio.py
 
 # c front end
 cat populated/pop_*.c >> trexio.c
@@ -21,6 +22,10 @@ cat populated/pop_*.f90  >> trexio_f.f90
 cat helper_fortran.f90 >> trexio_f.f90
 cat populated/pop_*.fh_90 >> trexio_f.f90
 
+# python front end
+cat basic_python.py >> trexio.py
+cat populated/pop_*.py >> trexio.py
+
 # suffixes
 cat suffix_s_front.h >> trexio_s.h
 cat suffix_front.h   >> trexio.h
diff --git a/src/templates_front/templator_front.org b/src/templates_front/templator_front.org
index 7434ed9c..312e4941 100644
--- a/src/templates_front/templator_front.org
+++ b/src/templates_front/templator_front.org
@@ -12,30 +12,13 @@
   #+NAME:header
   #+begin_src c
 /* This file was generated from the templator_front.org org-mode file.
-   To generate it, open trexio.org in Emacs and execute
+   To generate it, open templator_front.org in Emacs and execute
    M-x org-babel-tangle
 */
 
   #+end_src
-
-  #+begin_src f90 :tangle prefix_fortran.f90 :noweb yes
-module trexio
-
-  use, intrinsic :: iso_c_binding
-  implicit none
-
-  integer, parameter :: trexio_exit_code = 4
-  integer, parameter :: trexio_backend = 4
-
-  integer(trexio_backend), parameter :: TREXIO_HDF5 = 0
-  integer(trexio_backend), parameter :: TREXIO_TEXT = 1
-! integer(trexio_backend), parameter :: TREXIO_JSON = 2
-  integer(trexio_backend), parameter :: TREXIO_INVALID_BACK_END = 2
-
-  character(kind=c_char), parameter :: TREXIO_DELIM = c_new_line
-   #+end_src
-
-  #+end_src
+  
+** C
 
   #+begin_src c :tangle prefix_front.h :noweb yes
 <<header>>
@@ -72,11 +55,57 @@ typedef int32_t trexio_exit_code;
 #define _TREXIO_S_H
 
 #include "trexio.h"
+#include "trexio_private.h"
 #include <pthread.h>
-#include <assert.h>
 #include <stdbool.h>
   #+end_src
 
+  #+begin_src c :tangle trexio_private.h :noweb yes
+<<header>>
+#ifndef _TREXIO_PRIVATE_H
+#define _TREXIO_PRIVATE_H
+  #+end_src
+
+** Fortran
+
+  #+begin_src f90 :tangle prefix_fortran.f90
+module trexio
+
+  use, intrinsic :: iso_c_binding
+  implicit none
+
+  integer, parameter :: trexio_exit_code = 4
+  integer, parameter :: trexio_backend = 4
+
+  character(kind=c_char), parameter :: TREXIO_DELIM = c_new_line
+  #+end_src
+
+** Python
+
+  #+begin_src python :tangle prefix_python.py
+"""The Python API of the TREXIO library. 
+
+This package is a top-level wrapper of the SWIG-generated pytrexio module.
+"""
+
+
+try:
+    import pytrexio.pytrexio as pytr
+except ImportError:
+    raise Exception("Could not import pytrexio module from trexio package")
+
+# define max length of a string to be read, required for the low-level C routines
+PYTREXIO_MAX_STR_LENGTH = 2048
+
+# setuptools do not assign __version__ variable to the trexio package, so we set it manually
+from os import path
+__trexio_path__ = path.dirname(path.abspath(__file__))
+with open(path.join(path.join(__trexio_path__, 'pytrexio'), '_version.py')) as version_file:
+    __version__ = version_file.read().split('"')[1]
+
+__trexio_path__ = None
+  #+end_src
+
 * Coding conventions
 
   - integer types will be defined using types given in ~stdint.h~
@@ -113,45 +142,48 @@ typedef int32_t trexio_exit_code;
    #+end_src
 
 * Front end
-
+  
   All calls to TREXIO are thread-safe.
   TREXIO front end is modular, which simplifies implementation of new back ends.
 
 ** Error handling
 
       #+NAME: table-exit-codes
-   | Macro                        | Code | Description                          |
-   |------------------------------+------+--------------------------------------|
-   | ~TREXIO_FAILURE~             |   -1 | 'Unknown failure'                    |
-   | ~TREXIO_SUCCESS~             |    0 | 'Success'                            |
-   | ~TREXIO_INVALID_ARG_1~       |    1 | 'Invalid argument 1'                 |
-   | ~TREXIO_INVALID_ARG_2~       |    2 | 'Invalid argument 2'                 |
-   | ~TREXIO_INVALID_ARG_3~       |    3 | 'Invalid argument 3'                 |
-   | ~TREXIO_INVALID_ARG_4~       |    4 | 'Invalid argument 4'                 |
-   | ~TREXIO_INVALID_ARG_5~       |    5 | 'Invalid argument 5'                 |
-   | ~TREXIO_END~                 |    6 | 'End of file'                        |
-   | ~TREXIO_READONLY~            |    7 | 'Read-only file'                     |
-   | ~TREXIO_ERRNO~               |    8 | strerror(errno)                      |
-   | ~TREXIO_INVALID_ID~          |    9 | 'Invalid ID'                         |
-   | ~TREXIO_ALLOCATION_FAILED~   |   10 | 'Allocation failed'                  |
-   | ~TREXIO_HAS_NOT~             |   11 | 'Element absent'                     |
-   | ~TREXIO_INVALID_NUM~         |   12 | 'Invalid dimensions'                 |
-   | ~TREXIO_ATTR_ALREADY_EXISTS~ |   13 | 'Attribute (num/str) already exists' |
-   | ~TREXIO_DSET_ALREADY_EXISTS~ |   14 | 'Dataset already exists'             |
-   | ~TREXIO_OPEN_ERROR~          |   15 | 'Error opening file'                 |
-   | ~TREXIO_LOCK_ERROR~          |   16 | 'Error locking file'                 |
-   | ~TREXIO_UNLOCK_ERROR~        |   17 | 'Error unlocking file'               |
-   | ~TREXIO_FILE_ERROR~          |   18 | 'Invalid file handle'                |
-   | ~TREXIO_GROUP_READ_ERROR~    |   19 | 'Error reading group'                |
-   | ~TREXIO_GROUP_WRITE_ERROR~   |   20 | 'Error writing group'                |
-   | ~TREXIO_ELEM_READ_ERROR~     |   21 | 'Error reading element'              |
-   | ~TREXIO_ELEM_WRITE_ERROR~    |   22 | 'Error writing element'              |
-   | ~TREXIO_INVALID_STR_LEN~     |   30 | 'Invalid max_str_len'                |
+   | Macro                        | Code | Description                            |
+   |------------------------------+------+----------------------------------------|
+   | ~TREXIO_FAILURE~             |   -1 | 'Unknown failure'                      |
+   | ~TREXIO_SUCCESS~             |    0 | 'Success'                              |
+   | ~TREXIO_INVALID_ARG_1~       |    1 | 'Invalid argument 1'                   |
+   | ~TREXIO_INVALID_ARG_2~       |    2 | 'Invalid argument 2'                   |
+   | ~TREXIO_INVALID_ARG_3~       |    3 | 'Invalid argument 3'                   |
+   | ~TREXIO_INVALID_ARG_4~       |    4 | 'Invalid argument 4'                   |
+   | ~TREXIO_INVALID_ARG_5~       |    5 | 'Invalid argument 5'                   |
+   | ~TREXIO_END~                 |    6 | 'End of file'                          |
+   | ~TREXIO_READONLY~            |    7 | 'Read-only file'                       |
+   | ~TREXIO_ERRNO~               |    8 | strerror(errno)                        |
+   | ~TREXIO_INVALID_ID~          |    9 | 'Invalid ID'                           |
+   | ~TREXIO_ALLOCATION_FAILED~   |   10 | 'Allocation failed'                    |
+   | ~TREXIO_HAS_NOT~             |   11 | 'Element absent'                       |
+   | ~TREXIO_INVALID_NUM~         |   12 | 'Invalid dimensions'                   |
+   | ~TREXIO_ATTR_ALREADY_EXISTS~ |   13 | 'Attribute already exists'             |
+   | ~TREXIO_DSET_ALREADY_EXISTS~ |   14 | 'Dataset already exists'               |
+   | ~TREXIO_OPEN_ERROR~          |   15 | 'Error opening file'                   |
+   | ~TREXIO_LOCK_ERROR~          |   16 | 'Error locking file'                   |
+   | ~TREXIO_UNLOCK_ERROR~        |   17 | 'Error unlocking file'                 |
+   | ~TREXIO_FILE_ERROR~          |   18 | 'Invalid file handle'                  |
+   | ~TREXIO_GROUP_READ_ERROR~    |   19 | 'Error reading group'                  |
+   | ~TREXIO_GROUP_WRITE_ERROR~   |   20 | 'Error writing group'                  |
+   | ~TREXIO_ELEM_READ_ERROR~     |   21 | 'Error reading element'                |
+   | ~TREXIO_ELEM_WRITE_ERROR~    |   22 | 'Error writing element'                |
+   | ~TREXIO_UNSAFE_ARRAY_DIM~    |   23 | 'Access to memory beyond allocated'    |
+   | ~TREXIO_ATTR_MISSING~        |   24 | 'Attribute does not exist in the file' |
+   | ~TREXIO_DSET_MISSING~        |   25 | 'Dataset does not exist in the file'   |
+   | ~TREXIO_INVALID_STR_LEN~     |   30 | 'Invalid max_str_len'                  |
 
    # We need to force Emacs not to indent the Python code:
    # -*- org-src-preserve-indentation: t
 
-   #+begin_src python :var table=table-exit-codes :results drawer :exports none
+   #+begin_src python :var table=table-exit-codes :results drawer
 """ This script generates the C and Fortran constants for the error
     codes from the org-mode table.
 """
@@ -159,7 +191,7 @@ typedef int32_t trexio_exit_code;
 result = [ "#+begin_src c :tangle prefix_front.h :exports none" ]
 for (text, code,_) in table:
   text=text.replace("~","")
-  result += [ f"#define  {text:30s} ((trexio_exit_code) {code:d})" ]
+  result += [ f"#define {text:30s} ((trexio_exit_code) {code:d})" ]
 result += [ "#+end_src" ]
 
 result += [ "" ]
@@ -170,42 +202,54 @@ for (text, code,_) in table:
   result += [ f"   integer(trexio_exit_code), parameter :: {text:30s} = {code:d}" ]
 result += [ "#+end_src" ]
 
+result += [ "" ]
+
+result += [ "#+begin_src python :tangle prefix_python.py :exports none" ]
+result += [ "# define TREXIO exit codes" ]
+for (text, code,_) in table:
+  text=text.replace("~","")
+  result += [ f"{text:30s} = {code:d}" ]
+result += [ "#+end_src" ]
+
 return '\n'.join(result)
 
    #+end_src
 
 
    #+RESULTS:
-   :results:
-   #+begin_src c :tangle prefix_front.h
-   #define  TREXIO_FAILURE                 ((trexio_exit_code) -1)
-   #define  TREXIO_SUCCESS                 ((trexio_exit_code) 0)
-   #define  TREXIO_INVALID_ARG_1           ((trexio_exit_code) 1)
-   #define  TREXIO_INVALID_ARG_2           ((trexio_exit_code) 2)
-   #define  TREXIO_INVALID_ARG_3           ((trexio_exit_code) 3)
-   #define  TREXIO_INVALID_ARG_4           ((trexio_exit_code) 4)
-   #define  TREXIO_INVALID_ARG_5           ((trexio_exit_code) 5)
-   #define  TREXIO_END                     ((trexio_exit_code) 6)
-   #define  TREXIO_READONLY                ((trexio_exit_code) 7)
-   #define  TREXIO_ERRNO                   ((trexio_exit_code) 8)
-   #define  TREXIO_INVALID_ID              ((trexio_exit_code) 9)
-   #define  TREXIO_ALLOCATION_FAILED       ((trexio_exit_code) 10)
-   #define  TREXIO_HAS_NOT                 ((trexio_exit_code) 11)
-   #define  TREXIO_INVALID_NUM             ((trexio_exit_code) 12)
-   #define  TREXIO_ATTR_ALREADY_EXISTS     ((trexio_exit_code) 13)
-   #define  TREXIO_DSET_ALREADY_EXISTS     ((trexio_exit_code) 14)
-   #define  TREXIO_OPEN_ERROR              ((trexio_exit_code) 15)
-   #define  TREXIO_LOCK_ERROR              ((trexio_exit_code) 16)
-   #define  TREXIO_UNLOCK_ERROR            ((trexio_exit_code) 17)
-   #define  TREXIO_FILE_ERROR              ((trexio_exit_code) 18)
-   #define  TREXIO_GROUP_READ_ERROR        ((trexio_exit_code) 19)
-   #define  TREXIO_GROUP_WRITE_ERROR       ((trexio_exit_code) 20)
-   #define  TREXIO_ELEM_READ_ERROR         ((trexio_exit_code) 21)
-   #define  TREXIO_ELEM_WRITE_ERROR        ((trexio_exit_code) 22)
-   #define  TREXIO_INVALID_STR_LEN         ((trexio_exit_code) 30)
+   :RESULTS:
+   #+begin_src c :tangle prefix_front.h :exports none
+   #define TREXIO_FAILURE                 ((trexio_exit_code) -1)
+   #define TREXIO_SUCCESS                 ((trexio_exit_code) 0)
+   #define TREXIO_INVALID_ARG_1           ((trexio_exit_code) 1)
+   #define TREXIO_INVALID_ARG_2           ((trexio_exit_code) 2)
+   #define TREXIO_INVALID_ARG_3           ((trexio_exit_code) 3)
+   #define TREXIO_INVALID_ARG_4           ((trexio_exit_code) 4)
+   #define TREXIO_INVALID_ARG_5           ((trexio_exit_code) 5)
+   #define TREXIO_END                     ((trexio_exit_code) 6)
+   #define TREXIO_READONLY                ((trexio_exit_code) 7)
+   #define TREXIO_ERRNO                   ((trexio_exit_code) 8)
+   #define TREXIO_INVALID_ID              ((trexio_exit_code) 9)
+   #define TREXIO_ALLOCATION_FAILED       ((trexio_exit_code) 10)
+   #define TREXIO_HAS_NOT                 ((trexio_exit_code) 11)
+   #define TREXIO_INVALID_NUM             ((trexio_exit_code) 12)
+   #define TREXIO_ATTR_ALREADY_EXISTS     ((trexio_exit_code) 13)
+   #define TREXIO_DSET_ALREADY_EXISTS     ((trexio_exit_code) 14)
+   #define TREXIO_OPEN_ERROR              ((trexio_exit_code) 15)
+   #define TREXIO_LOCK_ERROR              ((trexio_exit_code) 16)
+   #define TREXIO_UNLOCK_ERROR            ((trexio_exit_code) 17)
+   #define TREXIO_FILE_ERROR              ((trexio_exit_code) 18)
+   #define TREXIO_GROUP_READ_ERROR        ((trexio_exit_code) 19)
+   #define TREXIO_GROUP_WRITE_ERROR       ((trexio_exit_code) 20)
+   #define TREXIO_ELEM_READ_ERROR         ((trexio_exit_code) 21)
+   #define TREXIO_ELEM_WRITE_ERROR        ((trexio_exit_code) 22)
+   #define TREXIO_UNSAFE_ARRAY_DIM        ((trexio_exit_code) 23)
+   #define TREXIO_ATTR_MISSING            ((trexio_exit_code) 24)
+   #define TREXIO_DSET_MISSING            ((trexio_exit_code) 25)
+   #define TREXIO_INVALID_STR_LEN         ((trexio_exit_code) 30)
    #+end_src
 
-   #+begin_src f90 :tangle prefix_fortran.f90
+   #+begin_src f90 :tangle prefix_fortran.f90 :exports none
       integer(trexio_exit_code), parameter :: TREXIO_FAILURE                 = -1
       integer(trexio_exit_code), parameter :: TREXIO_SUCCESS                 = 0
       integer(trexio_exit_code), parameter :: TREXIO_INVALID_ARG_1           = 1
@@ -230,16 +274,51 @@ return '\n'.join(result)
       integer(trexio_exit_code), parameter :: TREXIO_GROUP_WRITE_ERROR       = 20
       integer(trexio_exit_code), parameter :: TREXIO_ELEM_READ_ERROR         = 21
       integer(trexio_exit_code), parameter :: TREXIO_ELEM_WRITE_ERROR        = 22
+      integer(trexio_exit_code), parameter :: TREXIO_UNSAFE_ARRAY_DIM        = 23
+      integer(trexio_exit_code), parameter :: TREXIO_ATTR_MISSING            = 24
+      integer(trexio_exit_code), parameter :: TREXIO_DSET_MISSING            = 25
       integer(trexio_exit_code), parameter :: TREXIO_INVALID_STR_LEN         = 30
    #+end_src
-   :end:
+
+   #+begin_src python :tangle prefix_python.py :exports none
+   # define TREXIO exit codes
+   TREXIO_FAILURE                 = -1
+   TREXIO_SUCCESS                 = 0
+   TREXIO_INVALID_ARG_1           = 1
+   TREXIO_INVALID_ARG_2           = 2
+   TREXIO_INVALID_ARG_3           = 3
+   TREXIO_INVALID_ARG_4           = 4
+   TREXIO_INVALID_ARG_5           = 5
+   TREXIO_END                     = 6
+   TREXIO_READONLY                = 7
+   TREXIO_ERRNO                   = 8
+   TREXIO_INVALID_ID              = 9
+   TREXIO_ALLOCATION_FAILED       = 10
+   TREXIO_HAS_NOT                 = 11
+   TREXIO_INVALID_NUM             = 12
+   TREXIO_ATTR_ALREADY_EXISTS     = 13
+   TREXIO_DSET_ALREADY_EXISTS     = 14
+   TREXIO_OPEN_ERROR              = 15
+   TREXIO_LOCK_ERROR              = 16
+   TREXIO_UNLOCK_ERROR            = 17
+   TREXIO_FILE_ERROR              = 18
+   TREXIO_GROUP_READ_ERROR        = 19
+   TREXIO_GROUP_WRITE_ERROR       = 20
+   TREXIO_ELEM_READ_ERROR         = 21
+   TREXIO_ELEM_WRITE_ERROR        = 22
+   TREXIO_UNSAFE_ARRAY_DIM        = 23
+   TREXIO_ATTR_MISSING            = 24
+   TREXIO_DSET_MISSING            = 25
+   TREXIO_INVALID_STR_LEN         = 30
+   #+end_src
+   :END:
+ 
+*** Decoding errors
 
    The ~trexio_string_of_error~ converts an exit code into a string. The
    string is assumed to be large enough to contain the error message
    (typically 128 characters).
 
-◉ Decoding errors
-
    To decode the error messages, ~trexio_string_of_error~ converts an
    error code into a string.
 
@@ -263,9 +342,7 @@ result = []
 for (text, code, message) in table:
   text = text.replace("~","")
   message = message.replace("'",'"')
-  result += [ f"""case {text}:
-  return {message};
-  break;""" ]
+  result += [ f"""case {text}:\n  return {message};\n  break;""" ]
 return '\n'.join(result)
 
    #+end_src
@@ -314,8 +391,11 @@ return '\n'.join(result)
    case TREXIO_INVALID_NUM:
      return "Invalid dimensions";
      break;
-   case TREXIO_NUM_ALREADY_EXISTS:
-     return "Variable already exists";
+   case TREXIO_ATTR_ALREADY_EXISTS:
+     return "Attribute already exists";
+     break;
+   case TREXIO_DSET_ALREADY_EXISTS:
+     return "Dataset already exists";
      break;
    case TREXIO_OPEN_ERROR:
      return "Error opening file";
@@ -341,9 +421,22 @@ return '\n'.join(result)
    case TREXIO_ELEM_WRITE_ERROR:
      return "Error writing element";
      break;
+   case TREXIO_UNSAFE_ARRAY_DIM:
+     return "Access to memory beyond allocated";
+     break;
+   case TREXIO_ATTR_MISSING:
+     return "Attribute does not exist in the file";
+     break;
+   case TREXIO_DSET_MISSING:
+     return "Dataset does not exist in the file";
+     break;
+   case TREXIO_INVALID_STR_LEN:
+     return "Invalid max_str_len";
+     break;
    #+end_example
 
-   # Source
+**** C source code
+
    #+begin_src c :tangle prefix_front.c :noweb yes
 const char*
 trexio_string_of_error (const trexio_exit_code error)
@@ -361,7 +454,8 @@ trexio_string_of_error_f (const trexio_exit_code error, char result[<<MAX_STRING
 }
    #+end_src
 
-  # Fortran interface
+**** Fortran interface
+
   #+begin_src f90 :tangle prefix_fortran.f90 :noexport :noweb yes
 interface
    subroutine trexio_string_of_error (error, string) bind(C, name='trexio_string_of_error_f')
@@ -373,6 +467,40 @@ interface
 end interface
   #+end_src
 
+**** Python interface
+
+  #+begin_src python :tangle prefix_python.py :noexport
+class Error(Exception):
+    """Base class for TREXIO errors.
+
+    Attributes:
+       error: int -- exit code returned by the call to TREXIO library;
+       message: str -- decoded string corresponding to trexio_exit_code.
+
+    """
+
+    def __init__(self, trexio_return_code):
+        self.error = trexio_return_code
+        self.message = string_of_error(trexio_return_code)
+        super().__init__(self.message)
+
+
+def string_of_error(return_code: int) -> str:
+    """Decode the TREXIO exit code.
+
+    Argument is an integer return code that correspond to one of the TREXIO errors.
+
+    Returns string that contains description of TREXIO ~return_code~.
+    """
+
+    try:
+        error_str = pytr.trexio_string_of_error(return_code)
+    except:
+        raise 
+
+    return error_str
+  #+end_src
+
 ** Back ends
 
    TREXIO has several back ends:
@@ -386,6 +514,10 @@ end interface
    Then the corresponding back-end ~has/read/write~ functions has to be implemented. For example, see the commented
    lines that correspond to the ~TREXIO_JSON~ back end (not implemented yet).
 
+   _Note_: It is important to increment the value of TREXIO_INVALID_BACK_END when a new back end is added. Otherwise, it will not be available. 
+
+*** C
+
    #+begin_src c :tangle prefix_front.h
 typedef int32_t back_end_t;
 
@@ -396,6 +528,26 @@ typedef int32_t back_end_t;
 
 #define TREXIO_DELIM            "\n"
    #+end_src
+
+*** Fortran
+
+  #+begin_src f90 :tangle prefix_fortran.f90
+  integer(trexio_backend), parameter :: TREXIO_HDF5 = 0
+  integer(trexio_backend), parameter :: TREXIO_TEXT = 1
+! integer(trexio_backend), parameter :: TREXIO_JSON = 2
+  integer(trexio_backend), parameter :: TREXIO_INVALID_BACK_END = 2
+  #+end_src
+
+*** Python
+
+  #+begin_src python :tangle prefix_python.py
+# define TREXIO back ends
+TREXIO_HDF5 = 0
+TREXIO_TEXT = 1
+#TREXIO_JSON = 2
+TREXIO_INVALID_BACK_END = 2
+  #+end_src
+
 ** Read/write behavior
 
    Every time a reading function is called, the data is read from the
@@ -440,6 +592,76 @@ struct trexio_s {
   char              padding[6];   /* Ensures the proper alignment of back ends */
 };
    #+end_src
+
+*** TREXIO_File Python class
+
+   #+begin_src python :tangle basic_python.py
+class File:
+    """TREXIO File object.
+
+    General information about the TREXIO file.
+
+    Parameters:
+
+    filename: str
+        Is a name of the full path to the TREXIO file.
+    back_end: int
+        One of the currently supported TREXIO back ends.
+        For example, TREXIO_HDF5 (0) or TREXIO_TEXT (1).
+    mode: str
+        One of the currently supported TREXIO open modes.
+        For example, 'r' or 'w'.
+    isOpen: bool
+        Flag indicating whether the current object is still open for I/O
+    pytrexio_s:
+        A PyObject corresponding to SWIG proxy of the trexio_s struct in C.
+        This argument is in fact a TREXIO file handle, which is required for
+        communicating with the C back end. 
+    info: dict
+        Dictionary of key-value pairs with additional information about the file.
+    """
+
+
+    def __init__(self, filename, mode='r', back_end=TREXIO_HDF5,
+                 pytrexio_s=None, info=None):
+        """This is a TREXIO File constructor."""
+
+        self.filename = filename
+        self.mode = mode
+        self.back_end = back_end
+       
+        self.isOpen = False 
+        if pytrexio_s is None:
+            self.pytrexio_s = open(filename, mode, back_end)
+            self.isOpen = True
+        else:
+            self.pytrexio_s = pytrexio_s
+            self.isOpen = None
+
+        self.info = info
+
+
+    def close(self):
+        """Close a TREXIO File."""
+
+        if self.isOpen:
+            close(self.pytrexio_s)
+            self.isOpen = False
+        else:
+            raise Exception("TREXIO File object has not been opened.")
+
+
+    def __del__(self):
+        """This is a TREXIO File destructor."""
+
+        if self.isOpen:
+            close(self.pytrexio_s)
+        elif self.isOpen is None:
+            raise Exception("[WIP]: TREXIO file handle provided but what if the file is already closed?")
+        else:
+            pass 
+   #+end_src
+
 ** Polymorphism of the file handle
 
    Polymorphism of the ~trexio_t~ type is handled by ensuring that the
@@ -478,6 +700,8 @@ struct trexio_back_end_s {
    are hard-coded), which is why the user should tend to avoid
    renaming the ~.txt~ data files.
 
+*** C
+
    #+begin_src c :tangle prefix_front.h :exports none
 trexio_t* trexio_open(const char* file_name, const char mode, const back_end_t back_end);
    #+end_src
@@ -539,6 +763,10 @@ trexio_open(const char* file_name, const char mode,
   result->mode        = mode;
   result->one_based   = false;  // Need to be flipped in Fortran interface
   int irc = pthread_mutex_init ( &(result->thread_lock), NULL);
+  if (irc != 0) {
+    free(result);
+    return NULL;
+  }
   assert (irc == 0);
 
   trexio_exit_code rc;
@@ -627,6 +855,8 @@ trexio_open(const char* file_name, const char mode,
 }
    #+end_src
 
+*** Fortran
+
    #+begin_src f90 :tangle prefix_fortran.f90
 interface
    integer(8) function trexio_open_c (filename, mode, backend) bind(C, name="trexio_open")
@@ -639,6 +869,41 @@ interface
 end interface
    #+end_src
 
+*** Python
+
+   #+begin_src python :tangle basic_python.py
+def open(file_name: str, mode: str, back_end: int):
+    """Create TREXIO file or open existing one.
+
+    Parameters:
+
+    file_name: str
+        Name of the TREXIO file
+
+    mode: str
+        One of the currently supported ~open~ modes (e.g. 'w', 'r')
+
+    back_end: int
+        One of the currently supported TREXIO back ends (e.g. TREXIO_HDF5, TREXIO_TEXT)    
+
+    Return: 
+        SWIG object of type trexio_s.
+
+    Examples:
+    >>> from trexio import open as tr_open
+    >>> trex_file = tr_open("example.h5", "w", TREXIO_HDF5)
+    """
+
+    try:
+        trexio_file = pytr.trexio_open(file_name, mode, back_end)
+        assert trexio_file is not None
+    except AssertionError:
+        raise Exception(f"Could not open TREXIO file {file_name} using trexio_open function. Please make sure that there are no typos in the file name.")
+
+    return trexio_file
+   #+end_src
+
+*** Zero-based versus one-based arrays of indices
    Because arrays are zero-based in Fortran, we need to set a flag to
    know if we need to shift by 1 arrays of indices.
 
@@ -677,6 +942,8 @@ end interface
     output:
       ~trexio_exit_code~ exit code.
 
+*** C
+
     #+begin_src c :tangle prefix_front.h :exports none
 trexio_exit_code trexio_close(trexio_t* file);
     #+end_src
@@ -747,6 +1014,8 @@ trexio_close (trexio_t* file)
 }
     #+end_src
 
+*** Fortran
+
     #+begin_src f90 :tangle prefix_fortran.f90
 interface
    integer function trexio_close (trex_file) bind(C)
@@ -756,6 +1025,23 @@ interface
 end interface
     #+end_src
 
+*** Python
+
+   #+begin_src python :tangle basic_python.py
+def close(trexio_file):
+    """Close TREXIO file.
+
+    Parameter is a ~trexio_file~ object that has been created by a call to ~open~ function.
+    """
+
+    try:
+        rc = pytr.trexio_close(trexio_file)
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise
+   #+end_src
+
 * Templates for front end
 ** Description
 
@@ -804,6 +1090,7 @@ end interface
    | ~$group_dset_f_dtype_single$~  | Single precision datatype of the dataset [Fortran]  | ~real(4)/integer(4)~ |
    | ~$group_dset_f_dtype_double$~  | Double precision datatype of the dataset [Fortran]  | ~real(8)/integer(8)~ |
    | ~$group_dset_f_dims$~          | Dimensions in Fortran                               | ~(:,:)~              |
+   | ~$group_dset_py_dtype$~        | Standard datatype of the dataset [Python]           | ~float/int~          |
 
 
    Note: parent group name is always added to the child objects upon
@@ -864,6 +1151,7 @@ trexio_exit_code
 trexio_read_$group_num$_64 (trexio_t* const file, int64_t* const num)
 {
   if (file == NULL) return TREXIO_INVALID_ARG_1;
+  if (trexio_has_$group_num$(file) != TREXIO_SUCCESS) return TREXIO_ATTR_MISSING;
 
   uint64_t u_num = 0;
   trexio_exit_code rc = TREXIO_GROUP_READ_ERROR;
@@ -924,6 +1212,7 @@ trexio_exit_code
 trexio_read_$group_num$_32 (trexio_t* const file, int32_t* const num)
 {
   if (file == NULL) return TREXIO_INVALID_ARG_1;
+  if (trexio_has_$group_num$(file) != TREXIO_SUCCESS) return TREXIO_ATTR_MISSING;
 
   uint64_t u_num = 0;
   trexio_exit_code rc = TREXIO_GROUP_READ_ERROR;
@@ -1099,19 +1388,100 @@ interface
 end interface
      #+end_src
 
+*** Python templates for front end
+
+     #+begin_src python :tangle write_num_front.py
+def write_$group_num$(trexio_file, num_w: int) -> None: 
+    """Write the $group_num$ variable in the TREXIO file.
+
+    Parameters:
+
+    trexio_file:
+        TREXIO File object.
+
+    num_w: int
+        Value of the $group_num$ variable to be written.
+    
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc = pytr.trexio_write_$group_num$(trexio_file.pytrexio_s, num_w)
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise    
+     #+end_src
+
+     #+begin_src python :tangle read_num_front.py
+def read_$group_num$(trexio_file) -> int: 
+    """Read the $group_num$ variable from the TREXIO file.
+
+    Parameter is a ~TREXIO File~ object that has been created by a call to ~open~ function.
+
+    Returns:
+        ~num_r~: int
+        Integer value of $group_num$ variable read from ~trexio_file~.
+
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc, num_r = pytr.trexio_read_$group_num$(trexio_file.pytrexio_s)
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise    
+
+    return num_r
+     #+end_src
+
+     #+begin_src python :tangle has_num_front.py
+def has_$group_num$(trexio_file) -> bool: 
+    """Check that $group_num$ variable exists in the TREXIO file.
+
+    Parameter is a ~TREXIO File~ object that has been created by a call to ~open~ function.
+
+    Returns:
+          True if the variable exists, False otherwise
+
+    Raises:
+        - Exception from trexio.Error class if TREXIO return code ~rc~ is TREXIO_FAILURE and prints the error message using string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc = pytr.trexio_has_$group_num$(trexio_file.pytrexio_s)
+        if rc == TREXIO_FAILURE:
+            raise Error(rc)
+    except:
+        raise    
+
+    if rc == TREXIO_SUCCESS:
+        return True
+    else:
+        return False
+     #+end_src
+
 ** Templates for front end has/read/write a dataset of numerical data
 
    This section concerns API calls related to datasets.
 
-   | Function name                  | Description                         | Precision |
-   |--------------------------------+-------------------------------------+-----------|
-   | ~trexio_has_$group_dset$~      | Check if a dataset exists in a file | ---       |
-   | ~trexio_read_$group_dset$~     | Read  a dataset                     | Double    |
-   | ~trexio_write_$group_dset$~    | Write a dataset                     | Double    |
-   | ~trexio_read_$group_dset$_32~  | Read  a dataset                     | Single    |
-   | ~trexio_write_$group_dset$_32~ | Write a dataset                     | Single    |
-   | ~trexio_read_$group_dset$_64~  | Read  a dataset                     | Double    |
-   | ~trexio_write_$group_dset$_64~ | Write a dataset                     | Double    |
+   | Function name                    | Description                          | Precision                   |
+   |----------------------------------+--------------------------------------+-----------------------------|
+   | ~trexio_has_$group_dset$~        | Check if a dataset exists in a file  | ---                         |
+   | ~trexio_read_$group_dset$~       | Read  a dataset in default precision | Double/Single for float/int |
+   | ~trexio_write_$group_dset$~      | Write a dataset in default precision | Double/Single for float/int |
+   | ~trexio_read_safe_$group_dset$~  | Read  a bounded dataset              | Double                      |
+   | ~trexio_write_safe_$group_dset$~ | Write a bounded dataset              | Double                      |
+   | ~trexio_read_$group_dset$_32~    | Read  a dataset in single precision  | Single                      |
+   | ~trexio_write_$group_dset$_32~   | Write a dataset in single precision  | Single                      |
+   | ~trexio_read_$group_dset$_64~    | Read  a dataset in double precision  | Double                      |
+   | ~trexio_write_$group_dset$_64~   | Write a dataset in double precision  | Double                      |
 
 *** C templates for front end
 
@@ -1121,6 +1491,8 @@ end interface
    Suffixes ~_32~ and ~_64~ correspond to API calls dealing with single and double precision, respectively.
    The basic (non-suffixed) API call on datasets deals with double precision (see Table above).
 
+**** Function declarations
+
 
      #+begin_src c :tangle hrw_dset_data_front.h :exports none
 trexio_exit_code trexio_has_$group_dset$(trexio_t* const file);
@@ -1130,8 +1502,17 @@ trexio_exit_code trexio_read_$group_dset$_32(trexio_t* const file, $group_dset_d
 trexio_exit_code trexio_write_$group_dset$_32(trexio_t* const file, const $group_dset_dtype_single$* $group_dset$);
 trexio_exit_code trexio_read_$group_dset$_64(trexio_t* const file, $group_dset_dtype_double$* const $group_dset$);
 trexio_exit_code trexio_write_$group_dset$_64(trexio_t* const file, const $group_dset_dtype_double$* $group_dset$);
+trexio_exit_code trexio_read_safe_$group_dset$(trexio_t* const file, $group_dset_dtype_default$* const dset_out, const int64_t dim_out);
+trexio_exit_code trexio_write_safe_$group_dset$(trexio_t* const file, const $group_dset_dtype_default$* dset_in, const int64_t dim_in);
+trexio_exit_code trexio_read_safe_$group_dset$_32(trexio_t* const file, $group_dset_dtype_single$* const dset_out, const int64_t dim_out);
+trexio_exit_code trexio_write_safe_$group_dset$_32(trexio_t* const file, const $group_dset_dtype_single$* dset_in, const int64_t dim_in);
+trexio_exit_code trexio_read_safe_$group_dset$_64(trexio_t* const file, $group_dset_dtype_double$* const dset_out, const int64_t dim_out);
+trexio_exit_code trexio_write_safe_$group_dset$_64(trexio_t* const file, const $group_dset_dtype_double$* dset_in, const int64_t dim_in);
      #+end_src
 
+**** Source code for double precision functions
+
+
      #+begin_src c :tangle read_dset_data_64_front.c
 trexio_exit_code
 trexio_read_$group_dset$_64 (trexio_t* const file, $group_dset_dtype_double$* const $group_dset$)
@@ -1139,13 +1520,13 @@ trexio_read_$group_dset$_64 (trexio_t* const file, $group_dset_dtype_double$* co
 
   if (file  == NULL) return TREXIO_INVALID_ARG_1;
   if ($group_dset$ == NULL) return TREXIO_INVALID_ARG_2;
+  if (trexio_has_$group_dset$(file) != TREXIO_SUCCESS) return TREXIO_DSET_MISSING;
 
   trexio_exit_code rc;
   int64_t $group_dset_dim$ = 0;
 
   /* Error handling for this call is added by the generator */
   rc = trexio_read_$group_dset_dim$_64(file, &($group_dset_dim$));
-  if (rc != TREXIO_SUCCESS) return rc;
 
   if ($group_dset_dim$ == 0L) return TREXIO_INVALID_NUM;
 
@@ -1187,6 +1568,7 @@ trexio_read_$group_dset$_64 (trexio_t* const file, $group_dset_dtype_double$* co
 }
      #+end_src
 
+
      #+begin_src c :tangle write_dset_data_64_front.c
 trexio_exit_code
 trexio_write_$group_dset$_64 (trexio_t* const file, const $group_dset_dtype_double$* $group_dset$)
@@ -1201,7 +1583,6 @@ trexio_write_$group_dset$_64 (trexio_t* const file, const $group_dset_dtype_doub
 
   /* Error handling for this call is added by the generator */
   rc = trexio_read_$group_dset_dim$_64(file, &($group_dset_dim$));
-  if (rc != TREXIO_SUCCESS) return rc;
 
   if ($group_dset_dim$ == 0L) return TREXIO_INVALID_NUM;
 
@@ -1254,6 +1635,9 @@ trexio_write_$group_dset$_64 (trexio_t* const file, const $group_dset_dtype_doub
 }
      #+end_src
 
+**** Source code for single precision functions
+
+
      #+begin_src c :tangle read_dset_data_32_front.c
 trexio_exit_code
 trexio_read_$group_dset$_32 (trexio_t* const file, $group_dset_dtype_single$* const $group_dset$)
@@ -1261,6 +1645,7 @@ trexio_read_$group_dset$_32 (trexio_t* const file, $group_dset_dtype_single$* co
 
   if (file  == NULL) return TREXIO_INVALID_ARG_1;
   if ($group_dset$ == NULL) return TREXIO_INVALID_ARG_2;
+  if (trexio_has_$group_dset$(file) != TREXIO_SUCCESS) return TREXIO_DSET_MISSING;
 
   trexio_exit_code rc;
   int64_t $group_dset_dim$ = 0;
@@ -1321,6 +1706,7 @@ trexio_read_$group_dset$_32 (trexio_t* const file, $group_dset_dtype_single$* co
 }
      #+end_src
 
+
      #+begin_src c :tangle write_dset_data_32_front.c
 trexio_exit_code
 trexio_write_$group_dset$_32 (trexio_t* const file, const $group_dset_dtype_single$* $group_dset$)
@@ -1387,6 +1773,119 @@ trexio_write_$group_dset$_32 (trexio_t* const file, const $group_dset_dtype_sing
 }
      #+end_src
 
+**** Source code for memory-safe functions
+
+  #+NAME:dimCheck
+  #+begin_src c
+  trexio_exit_code rc;
+  int64_t $group_dset_dim$ = 0;
+
+  /* Error handling for this call is added by the generator */
+  rc = trexio_read_$group_dset_dim$_64(file, &($group_dset_dim$));
+
+  if ($group_dset_dim$ == 0L) return TREXIO_INVALID_NUM;
+
+  uint32_t rank = $group_dset_rank$;
+  uint64_t dims[$group_dset_rank$] = {$group_dset_dim_list$};
+
+  /* The block below is specific to safe API as it checks the boundaries */
+  uint64_t dim_size = 1;
+  for (uint32_t i=0; i<rank; ++i){
+      dim_size *= dims[i];
+  }
+  #+end_src
+
+
+     #+begin_src c :tangle read_dset_data_32_safe_front.c :noweb yes
+trexio_exit_code
+trexio_read_safe_$group_dset$_32 (trexio_t* const file, $group_dset_dtype_single$* const dset_out, const int64_t dim_out)
+{
+
+  if (file  == NULL) return TREXIO_INVALID_ARG_1;
+  if (dset_out == NULL) return TREXIO_INVALID_ARG_2;
+  if (trexio_has_$group_dset$(file) != TREXIO_SUCCESS) return TREXIO_DSET_MISSING;
+
+<<dimCheck>>
+  
+  if (dim_out > (int64_t) dim_size) return TREXIO_UNSAFE_ARRAY_DIM;
+
+  return trexio_read_$group_dset$_32(file, dset_out);
+}
+     #+end_src
+
+
+     #+begin_src c :tangle write_dset_data_32_safe_front.c :noweb yes
+trexio_exit_code
+trexio_write_safe_$group_dset$_32 (trexio_t* const file, const $group_dset_dtype_single$* dset_in, const int64_t dim_in)
+{
+
+  if (file  == NULL) return TREXIO_INVALID_ARG_1;
+  if (dset_in == NULL) return TREXIO_INVALID_ARG_2;
+  if (trexio_has_$group_dset$(file) == TREXIO_SUCCESS) return TREXIO_DSET_ALREADY_EXISTS;
+
+<<dimCheck>>
+
+  if (dim_in > (int64_t) dim_size) return TREXIO_UNSAFE_ARRAY_DIM;
+
+  return trexio_write_$group_dset$_32(file, dset_in);
+}
+     #+end_src
+
+     #+begin_src c :tangle read_dset_data_64_safe_front.c :noweb yes
+trexio_exit_code
+trexio_read_safe_$group_dset$_64 (trexio_t* const file, $group_dset_dtype_double$* const dset_out, const int64_t dim_out)
+{
+
+  if (file  == NULL) return TREXIO_INVALID_ARG_1;
+  if (dset_out == NULL) return TREXIO_INVALID_ARG_2;
+  if (trexio_has_$group_dset$(file) != TREXIO_SUCCESS) return TREXIO_DSET_MISSING;
+
+<<dimCheck>>
+  
+  if (dim_out > (int64_t) dim_size) return TREXIO_UNSAFE_ARRAY_DIM;
+
+  return trexio_read_$group_dset$_64(file, dset_out);
+}
+     #+end_src
+
+
+     #+begin_src c :tangle write_dset_data_64_safe_front.c :noweb yes
+trexio_exit_code
+trexio_write_safe_$group_dset$_64 (trexio_t* const file, const $group_dset_dtype_double$* dset_in, const int64_t dim_in)
+{
+
+  if (file  == NULL) return TREXIO_INVALID_ARG_1;
+  if (dset_in == NULL) return TREXIO_INVALID_ARG_2;
+  if (trexio_has_$group_dset$(file) == TREXIO_SUCCESS) return TREXIO_DSET_ALREADY_EXISTS;
+
+<<dimCheck>>
+
+  if (dim_in > (int64_t) dim_size) return TREXIO_UNSAFE_ARRAY_DIM;
+
+  return trexio_write_$group_dset$_64(file, dset_in);
+}
+     #+end_src
+
+**** Source code for default functions
+
+     #+begin_src c :tangle read_dset_data_safe_def_front.c
+trexio_exit_code
+trexio_read_safe_$group_dset$ (trexio_t* const file, $group_dset_dtype_default$* const $group_dset$, const int64_t dim_out)
+{
+  return trexio_read_safe_$group_dset$_$default_prec$(file, $group_dset$, dim_out);
+}
+     #+end_src
+
+
+     #+begin_src c :tangle write_dset_data_safe_def_front.c
+trexio_exit_code
+trexio_write_safe_$group_dset$ (trexio_t* const file, const $group_dset_dtype_default$* $group_dset$, const int64_t dim_in)
+{
+  return trexio_write_safe_$group_dset$_$default_prec$(file, $group_dset$, dim_in);
+}
+     #+end_src
+
+
      #+begin_src c :tangle read_dset_data_def_front.c
 trexio_exit_code
 trexio_read_$group_dset$ (trexio_t* const file, $group_dset_dtype_default$* const $group_dset$)
@@ -1395,6 +1894,7 @@ trexio_read_$group_dset$ (trexio_t* const file, $group_dset_dtype_default$* cons
 }
      #+end_src
 
+
      #+begin_src c :tangle write_dset_data_def_front.c
 trexio_exit_code
 trexio_write_$group_dset$ (trexio_t* const file, const $group_dset_dtype_default$* $group_dset$)
@@ -1403,6 +1903,7 @@ trexio_write_$group_dset$ (trexio_t* const file, const $group_dset_dtype_default
 }
      #+end_src
 
+
      #+begin_src c :tangle has_dset_data_front.c
 trexio_exit_code
 trexio_has_$group_dset$ (trexio_t* const file)
@@ -1505,6 +2006,195 @@ interface
 end interface
      #+end_src
 
+*** Python templates for front end
+
+     #+begin_src python :tangle write_dset_data_front.py
+def write_$group_dset$(trexio_file, dset_w) -> None: 
+    """Write the $group_dset$ array of numbers in the TREXIO file.
+
+    Parameters:
+
+    trexio_file:
+        TREXIO File object.
+
+    dset_w: list OR numpy.ndarray
+        Array of $group_dset$ values to be written. If array data type does not correspond to int64 or float64, the conversion is performed.
+    
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        import numpy as np
+    except ImportError:
+        raise Exception("NumPy cannot be imported.")
+
+    doConversion = False
+    doFlatten = False
+    if not isinstance(dset_w, (list, tuple)):
+       # if input array is not a list or tuple then it is probably a numpy array
+       if isinstance(dset_w, np.ndarray) and (not dset_w.dtype==np.int64 or not dset_w.dtype==np.float64):
+           doConversion = True
+
+       if len(dset_w.shape) > 1:
+           doFlatten = True
+           if doConversion:
+               dset_64 = np.$group_dset_py_dtype$64(dset_w).flatten()
+           else:
+               dset_flat = np.array(dset_w, dtype=np.$group_dset_py_dtype$64).flatten()
+       else:
+           if doConversion:
+               dset_64 = np.$group_dset_py_dtype$64(dset_w)
+           
+    else:
+       # if input array is a multidimensional list or tuple, we have to convert it
+       try:
+           doFlatten = True
+           ncol = len(dset_w[0])
+           dset_flat = np.array(dset_w, dtype=np.$group_dset_py_dtype$64).flatten()
+       except TypeError:
+           doFlatten = False
+           pass
+
+
+    if doConversion:
+        rc = pytr.trexio_write_safe_$group_dset$_64(trexio_file.pytrexio_s, dset_64)
+    elif doFlatten:
+        rc = pytr.trexio_write_safe_$group_dset$_64(trexio_file.pytrexio_s, dset_flat)
+    else:
+        rc = pytr.trexio_write_safe_$group_dset$_64(trexio_file.pytrexio_s, dset_w)
+    
+    if rc != TREXIO_SUCCESS:
+        raise Error(rc)
+     #+end_src
+
+     #+begin_src python :tangle read_dset_data_front.py
+def read_$group_dset$(trexio_file, dim = None, doReshape = None, dtype = None): 
+    """Read the $group_dset$ array of numbers from the TREXIO file.
+
+    Parameters:
+
+    trexio_file:
+        TREXIO File object.
+
+    dim (Optional): int
+        Size of the block to be read from the file (i.e. how many items of $group_dset$ will be returned)
+        If None, the function will read all necessary array dimensions from the file.
+
+    dtype (Optional): type
+        NumPy data type of the output (e.g. np.int32|int16 or np.float32|float16). If specified, the output array will be converted from the default double precision.
+    
+    doReshape (Optional): bool
+        Flag to determine whether the output NumPy array has be reshaped or not. Be default, reshaping is performed
+        based on the dimensions from the ~trex.json~ file. Otherwise, ~shape~ array (list or tuple) is used if provided by the user.
+
+    Returns:
+        ~dset_64~ if dtype is None or ~dset_converted~ otherwise: numpy.ndarray 
+        1D NumPy array with ~dim~ elements corresponding to $group_dset$ values read from the TREXIO file.
+
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+"""
+
+    try:
+        import numpy as np
+    except ImportError:
+        raise Exception("NumPy cannot be imported.")
+
+    if doReshape is None:
+        doReshape = True
+        
+    # if dim is not specified, read dimensions from the TREXIO file
+    dims_list = None
+    if dim is None or doReshape:
+        $group_dset_dim$ = read_$group_dset_dim$(trexio_file) 
+        
+        dims_list = [$group_dset_dim_list$]
+        dim = 1
+        for i in range($group_dset_rank$):
+            dim *= dims_list[i]
+
+        
+    shape = tuple(dims_list)
+    if shape is None and doReshape:
+        raise ValueError("Reshaping failure: shape is None.")
+
+    try:
+        rc, dset_64 = pytr.trexio_read_safe_$group_dset$_64(trexio_file.pytrexio_s, dim)
+
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise
+
+
+    isConverted = False
+    dset_converted = None
+    if dtype is not None:
+        
+        try:
+            assert isinstance(dtype, type)
+        except AssertionError:
+            raise TypeError("dtype argument has to be an instance of the type class (e.g. np.float32).")
+
+
+        if not dtype==np.int64 or not dtype==np.float64:
+            try:
+                dset_converted = np.array(dset_64, dtype=dtype)
+            except:
+                raise
+                
+            isConverted = True
+
+    # additional assert can be added here to check that read_safe functions returns numpy array of proper dimension
+
+    if doReshape:
+        try:
+            # in-place reshaping did not work so I have to make a copy
+            if isConverted:    
+                dset_reshaped = np.reshape(dset_converted, shape, order='C')
+            else:
+                dset_reshaped = np.reshape(dset_64, shape, order='C')
+        except:
+            raise
+
+    if isConverted:    
+        return dset_converted
+    elif doReshape:
+        return dset_reshaped
+    else:
+        return dset_64
+     #+end_src
+
+     #+begin_src python :tangle has_dset_data_front.py
+def has_$group_dset$(trexio_file) -> bool: 
+    """Check that $group_dset$ variable exists in the TREXIO file.
+
+    Parameter is a ~TREXIO File~ object that has been created by a call to ~open~ function.
+
+    Returns:
+          True if the variable exists, False otherwise
+
+    Raises:
+        - Exception from trexio.Error class if TREXIO return code ~rc~ is TREXIO_FAILURE and prints the error message using string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc = pytr.trexio_has_$group_dset$(trexio_file.pytrexio_s)
+        if rc == TREXIO_FAILURE:
+            raise Error(rc)
+    except:
+        raise    
+
+    if rc == TREXIO_SUCCESS:
+        return True
+    else:
+        return False
+     #+end_src
+
 ** Sparse data structures
 
    Sparse data structures are used typically for large tensors such as
@@ -1637,20 +2327,21 @@ trexio_read_chunk_ao_2e_int_eri_value_64(trexio_t* const file,
 
      #+begin_src c :tangle hrw_dset_str_front.h :exports none
 trexio_exit_code trexio_has_$group_dset$(trexio_t* const file);
-trexio_exit_code trexio_read_$group_dset$_low(trexio_t* const file, char* dset, const uint32_t max_str_len);
-trexio_exit_code trexio_write_$group_dset$_low(trexio_t* const file, const char* dset, const uint32_t max_str_len);
-trexio_exit_code trexio_read_$group_dset$(trexio_t* const file, char** dset, const uint32_t max_str_len);
-trexio_exit_code trexio_write_$group_dset$(trexio_t* const file, const char** dset, const uint32_t max_str_len);
+trexio_exit_code trexio_read_$group_dset$_low(trexio_t* const file, char* dset_out, const int32_t max_str_len);
+trexio_exit_code trexio_write_$group_dset$_low(trexio_t* const file, const char* dset_in, const int32_t max_str_len);
+trexio_exit_code trexio_read_$group_dset$(trexio_t* const file, char** dset_out, const int32_t max_str_len);
+trexio_exit_code trexio_write_$group_dset$(trexio_t* const file, const char** dset_in, const int32_t max_str_len);
      #+end_src
 
      #+begin_src c :tangle read_dset_str_front.c
 trexio_exit_code
-trexio_read_$group_dset$_low (trexio_t* const file, char* dset, const uint32_t max_str_len)
+trexio_read_$group_dset$_low (trexio_t* const file, char* dset_out, const int32_t max_str_len)
 {
 
   if (file  == NULL) return TREXIO_INVALID_ARG_1;
-  if (dset == NULL) return TREXIO_INVALID_ARG_2;
+  if (dset_out == NULL) return TREXIO_INVALID_ARG_2;
   if (max_str_len <= 0) return TREXIO_INVALID_ARG_3;
+  if (trexio_has_$group_dset$(file) != TREXIO_SUCCESS) return TREXIO_DSET_MISSING;
 
   trexio_exit_code rc;
   int64_t $group_dset_dim$ = 0;
@@ -1667,15 +2358,15 @@ trexio_read_$group_dset$_low (trexio_t* const file, char* dset, const uint32_t m
   switch (file->back_end) {
 
   case TREXIO_TEXT:
-    return trexio_text_read_$group_dset$(file, dset, rank, dims, max_str_len);
+    return trexio_text_read_$group_dset$(file, dset_out, rank, dims, (uint32_t) max_str_len);
     break;
 
   case TREXIO_HDF5:
-    return trexio_hdf5_read_$group_dset$(file, dset, rank, dims, max_str_len);
+    return trexio_hdf5_read_$group_dset$(file, dset_out, rank, dims, (uint32_t) max_str_len);
     break;
 /*
   case TREXIO_JSON:
-    rc = trexio_json_read_$group_dset$(file, dset, rank, dims);
+    rc = trexio_json_read_$group_dset$(file, dset_out, rank, dims);
     break;
 ,*/
   }
@@ -1684,12 +2375,13 @@ trexio_read_$group_dset$_low (trexio_t* const file, char* dset, const uint32_t m
 }
 
 trexio_exit_code
-trexio_read_$group_dset$ (trexio_t* const file, char** dset, const uint32_t max_str_len)
+trexio_read_$group_dset$ (trexio_t* const file, char** dset_out, const int32_t max_str_len)
 {
 
   if (file  == NULL) return TREXIO_INVALID_ARG_1;
-  if (dset == NULL) return TREXIO_INVALID_ARG_2;
+  if (dset_out == NULL) return TREXIO_INVALID_ARG_2;
   if (max_str_len <= 0) return TREXIO_INVALID_ARG_3;
+  if (trexio_has_$group_dset$(file) != TREXIO_SUCCESS) return TREXIO_DSET_MISSING;
 
   assert(file->back_end < TREXIO_INVALID_BACK_END);
 
@@ -1710,17 +2402,17 @@ trexio_read_$group_dset$ (trexio_t* const file, char** dset, const uint32_t max_
     return rc;
   }
 
-  char * pch;
-  for (uint64_t i=0; i < dset_dim; i++) {
+  for (uint64_t i=0; i < (uint64_t) dset_dim; i++) {
 
+    char * pch;
     pch = i == 0 ? strtok(str_compiled, TREXIO_DELIM) : strtok(NULL, TREXIO_DELIM) ;
     if (pch == NULL) {
       FREE(str_compiled);
       return TREXIO_FAILURE;
     }
 
-    strcpy(dset[i], "");
-    strcat(dset[i], pch);
+    strcpy(dset_out[i], "");
+    strcat(dset_out[i], pch);
 
   }
 
@@ -1732,11 +2424,11 @@ trexio_read_$group_dset$ (trexio_t* const file, char** dset, const uint32_t max_
 
      #+begin_src c :tangle write_dset_str_front.c
 trexio_exit_code
-trexio_write_$group_dset$_low (trexio_t* const file, const char* dset, const uint32_t max_str_len)
+trexio_write_$group_dset$_low (trexio_t* const file, const char* dset_in, const int32_t max_str_len)
 {
 
   if (file  == NULL) return TREXIO_INVALID_ARG_1;
-  if (dset == NULL) return TREXIO_INVALID_ARG_2;
+  if (dset_in == NULL) return TREXIO_INVALID_ARG_2;
   if (max_str_len <= 0) return TREXIO_INVALID_ARG_3;
   if (trexio_has_$group_dset$(file) == TREXIO_SUCCESS) return TREXIO_DSET_ALREADY_EXISTS;
 
@@ -1756,14 +2448,16 @@ trexio_write_$group_dset$_low (trexio_t* const file, const char* dset, const uin
   char*  tmp_str  = CALLOC(dims[0]*(max_str_len+1), char);
   if (tmp_str == NULL) return TREXIO_ALLOCATION_FAILED;
   char** dset_str = CALLOC(dims[0], char*);
-  if (dset_str == NULL) return TREXIO_ALLOCATION_FAILED;
+  if (dset_str == NULL) {
+    FREE(tmp_str);
+    return TREXIO_ALLOCATION_FAILED;
+  }
 
-  char* pch;
-  size_t pch_len;
   /* parse the string using strtok */
   for(uint64_t i=0; i<dims[0]; i++) {
 
-    pch = i == 0 ? strtok( (char*) dset, TREXIO_DELIM) : strtok(NULL, TREXIO_DELIM) ;
+    char* pch;
+    pch = i == 0 ? strtok( (char*) dset_in, TREXIO_DELIM) : strtok(NULL, TREXIO_DELIM) ;
 
     if (pch == NULL) {
       FREE(dset_str[0]);
@@ -1771,9 +2465,9 @@ trexio_write_$group_dset$_low (trexio_t* const file, const char* dset, const uin
       return TREXIO_FAILURE;
     }
 
-    pch_len = strlen(pch);
+    size_t pch_len = strlen(pch) + 1;
 
-    if (pch_len > max_str_len) {
+    if (pch_len > (size_t) max_str_len) {
       FREE(dset_str[0]);
       FREE(dset_str);
       return TREXIO_INVALID_STR_LEN;
@@ -1809,11 +2503,11 @@ trexio_write_$group_dset$_low (trexio_t* const file, const char* dset, const uin
 }
 
 trexio_exit_code
-trexio_write_$group_dset$ (trexio_t* const file, const char** dset, const uint32_t max_str_len)
+trexio_write_$group_dset$ (trexio_t* const file, const char** dset_in, const int32_t max_str_len)
 {
 
   if (file  == NULL) return TREXIO_INVALID_ARG_1;
-  if (dset == NULL) return TREXIO_INVALID_ARG_2;
+  if (dset_in == NULL) return TREXIO_INVALID_ARG_2;
   if (max_str_len <= 0) return TREXIO_INVALID_ARG_3;
   if (trexio_has_$group_dset$(file) == TREXIO_SUCCESS) return TREXIO_DSET_ALREADY_EXISTS;
 
@@ -1831,8 +2525,8 @@ trexio_write_$group_dset$ (trexio_t* const file, const char** dset, const uint32
   if (str_compiled == NULL) return TREXIO_ALLOCATION_FAILED;
 
   strcpy(str_compiled, "");
-  for (uint64_t i=0; i < dset_dim; i++) {
-    strcat(str_compiled, dset[i]);
+  for (uint64_t i=0; i < (uint64_t) dset_dim; i++) {
+    strcat(str_compiled, dset_in[i]);
     strcat(str_compiled, TREXIO_DELIM);
   }
 
@@ -1960,6 +2654,116 @@ end interface
    end function trexio_write_$group_dset$
   #+end_src
 
+*** Python templates for front end
+
+     #+begin_src python :tangle write_dset_str_front.py
+def write_$group_dset$(trexio_file, dset_w: list) -> None: 
+    """Write the $group_dset$ array of strings in the TREXIO file.
+
+    Parameters:
+
+    trexio_file:
+        TREXIO File object.
+
+    dset_w: list
+        Array of $group_dset$ strings to be written.
+    
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    max_str_length = len(max(dset_w, key=len)) + 1
+
+    try:
+        rc = pytr.trexio_write_$group_dset$(trexio_file.pytrexio_s, dset_w, max_str_length)
+
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise    
+
+     #+end_src
+
+     #+begin_src python :tangle read_dset_str_front.py
+def read_$group_dset$(trexio_file, dim = None) -> list: 
+    """Read the $group_dset$ array of strings from the TREXIO file.
+
+    Parameters:
+
+    trexio_file:
+        TREXIO File object.
+
+    dim (Optional): int
+        Size of the block to be read from the file (i.e. how many items of $group_dset$ will be returned)
+        If None, the function will read all necessary array dimensions from the file.
+
+    Returns:
+        ~dset_r~: list
+        1D list with ~dim~ elements corresponding to $group_dset$ strings read from the TREXIO file.
+
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    # if dim is not specified, read dimensions from the TREXIO file
+    if dim is None:
+        $group_dset_dim$ = read_$group_dset_dim$(trexio_file) 
+        
+        dims_list = [$group_dset_dim_list$]
+        dim = 1
+        for i in range($group_dset_rank$):
+            dim *= dims_list[i]
+
+
+    try:
+        rc, dset_1d_r = pytr.trexio_read_$group_dset$_low(trexio_file.pytrexio_s, PYTREXIO_MAX_STR_LENGTH)
+
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise    
+
+
+    try:
+        dset_full = dset_1d_r.split(pytr.TREXIO_DELIM)
+        dset_2d_r = [dset_full[i] for i in range(dim) if dset_full[i]]
+        if not dset_2d_r:
+            raise ValueError(f"Output of {read_$group_dset$.__name__} function cannot be an empty list.")
+    except:
+        raise
+
+    return dset_2d_r
+     #+end_src
+
+     #+begin_src python :tangle has_dset_str_front.py
+def has_$group_dset$(trexio_file) -> bool: 
+    """Check that $group_dset$ variable exists in the TREXIO file.
+
+    Parameter is a ~TREXIO File~ object that has been created by a call to ~open~ function.
+
+    Returns:
+          True if the variable exists, False otherwise
+
+    Raises:
+        - Exception from trexio.Error class if TREXIO return code ~rc~ is TREXIO_FAILURE and prints the error message using string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc = pytr.trexio_has_$group_dset$(trexio_file.pytrexio_s)
+        if rc == TREXIO_FAILURE:
+            raise Error(rc)
+    except:
+        raise    
+
+    if rc == TREXIO_SUCCESS:
+        return True
+    else:
+        return False
+     #+end_src
+
 ** Templates for front end has/read/write a single string attribute
 *** Introduction
 
@@ -1975,29 +2779,28 @@ end interface
 
      #+begin_src c :tangle hrw_attr_str_front.h :exports none
 trexio_exit_code trexio_has_$group_str$(trexio_t* const file);
-trexio_exit_code trexio_read_$group_str$(trexio_t* const file, char* const str, const uint32_t max_str_len);
-trexio_exit_code trexio_write_$group_str$(trexio_t* const file, const char* str, const uint32_t max_str_len);
+trexio_exit_code trexio_read_$group_str$(trexio_t* const file, char* const str_out, const int32_t max_str_len);
+trexio_exit_code trexio_write_$group_str$(trexio_t* const file, const char* str, const int32_t max_str_len);
      #+end_src
 
      #+begin_src c :tangle read_attr_str_front.c
 trexio_exit_code
-trexio_read_$group_str$ (trexio_t* const file, char* const str, const uint32_t max_str_len)
+trexio_read_$group_str$ (trexio_t* const file, char* const str_out, const int32_t max_str_len)
 {
 
   if (file == NULL) return TREXIO_INVALID_ARG_1;
-  if (str  == NULL) return TREXIO_INVALID_ARG_2;
+  if (str_out  == NULL) return TREXIO_INVALID_ARG_2;
   if (max_str_len <= 0) return TREXIO_INVALID_ARG_3;
-
-  trexio_exit_code rc = TREXIO_FAILURE;
+  if (trexio_has_$group_str$(file) != TREXIO_SUCCESS) return TREXIO_ATTR_MISSING;
 
   switch (file->back_end) {
 
   case TREXIO_TEXT:
-    return trexio_text_read_$group_str$(file, str, max_str_len);
+    return trexio_text_read_$group_str$(file, str_out, (uint32_t) max_str_len);
     break;
 
   case TREXIO_HDF5:
-    return trexio_hdf5_read_$group_str$(file, str, max_str_len);
+    return trexio_hdf5_read_$group_str$(file, str_out, (uint32_t) max_str_len);
     break;
 /*
   case TREXIO_JSON:
@@ -2012,7 +2815,7 @@ trexio_read_$group_str$ (trexio_t* const file, char* const str, const uint32_t m
 
      #+begin_src c :tangle write_attr_str_front.c
 trexio_exit_code
-trexio_write_$group_str$ (trexio_t* const file, const char* str, const uint32_t max_str_len)
+trexio_write_$group_str$ (trexio_t* const file, const char* str, const int32_t max_str_len)
 {
 
   if (file == NULL) return TREXIO_INVALID_ARG_1;
@@ -2021,7 +2824,7 @@ trexio_write_$group_str$ (trexio_t* const file, const char* str, const uint32_t
   if (trexio_has_$group_str$(file) == TREXIO_SUCCESS) return TREXIO_ATTR_ALREADY_EXISTS;
 
   size_t len_write = strlen(str);
-  if (max_str_len < len_write) return TREXIO_INVALID_STR_LEN;  
+  if ((size_t) max_str_len < len_write) return TREXIO_INVALID_STR_LEN;  
 
   switch (file->back_end) {
 
@@ -2140,6 +2943,89 @@ end interface
    end function trexio_write_$group_str$
   #+end_src
 
+*** Python templates for front end
+
+     #+begin_src python :tangle write_attr_str_front.py
+def write_$group_str$(trexio_file, str_w: str) -> None: 
+    """Write the $group_str$ variable in the TREXIO file.
+
+    Parameters:
+
+    trexio_file:
+        TREXIO File object.
+
+    str_w: str
+        String corresponding to the $group_str$ variable to be written.
+    
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    max_str_length = len(str_w) + 1
+
+    try:
+        rc = pytr.trexio_write_$group_str$(trexio_file.pytrexio_s, str_w, max_str_length)
+
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise    
+     #+end_src
+
+     #+begin_src python :tangle read_attr_str_front.py
+def read_$group_str$(trexio_file) -> str: 
+    """Read the $group_str$ variable from the TREXIO file.
+
+    Parameter is a ~TREXIO File~ object that has been created by a call to ~open~ function.
+
+    Returns:
+        ~str_r~: str
+        String corresponding to the $group_str$ variable read from ~trexio_file~.
+
+    Raises:
+        - Exception from AssertionError if TREXIO return code ~rc~ is different from TREXIO_SUCCESS and prints the error message using trexio_string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc, str_r = pytr.trexio_read_$group_str$(trexio_file.pytrexio_s, PYTREXIO_MAX_STR_LENGTH)
+
+        if rc != TREXIO_SUCCESS:
+            raise Error(rc)
+    except:
+        raise    
+
+    return str_r
+     #+end_src
+     
+     #+begin_src python :tangle has_attr_str_front.py
+def has_$group_str$(trexio_file) -> bool: 
+    """Check that $group_str$ variable exists in the TREXIO file.
+
+    Parameter is a ~TREXIO File~ object that has been created by a call to ~open~ function.
+
+    Returns:
+          True if the variable exists, False otherwise
+
+    Raises:
+        - Exception from trexio.Error class if TREXIO return code ~rc~ is TREXIO_FAILURE and prints the error message using string_of_error.
+        - Exception from some other error (e.g. RuntimeError).
+    """
+
+    try:
+        rc = pytr.trexio_has_$group_str$(trexio_file.pytrexio_s)
+        if rc == TREXIO_FAILURE:
+            raise Error(rc)
+    except:
+        raise    
+
+    if rc == TREXIO_SUCCESS:
+        return True
+    else:
+        return False
+     #+end_src
+
 * Fortran helper/wrapper functions
 
   The function below adapts the original C-based ~trexio_open~ for Fortran.
@@ -2268,6 +3154,10 @@ contains
 #endif
   #+end_src
 
+  #+begin_src c :tangle trexio_private.h
+#endif
+  #+end_src
+
   #+begin_src f90 :tangle suffix_fortran.f90
 end module trexio
   #+end_src
diff --git a/src/templates_hdf5/templator_hdf5.org b/src/templates_hdf5/templator_hdf5.org
index 7550c390..90a9881d 100644
--- a/src/templates_hdf5/templator_hdf5.org
+++ b/src/templates_hdf5/templator_hdf5.org
@@ -64,9 +64,8 @@
   #+begin_src c :tangle struct_hdf5.h
 typedef struct trexio_hdf5_s {
   trexio_t     parent ;
-  hid_t      file_id;
-  hid_t      $group$_group;
-  const char* file_name;
+  hid_t        file_id;
+  hid_t        $group$_group;
 } trexio_hdf5_t;
   #+end_src
 
@@ -537,15 +536,22 @@ trexio_hdf5_write_$group_dset$ (trexio_t* const file, const char** $group_dset$,
 
   /* we are going to write variable-length strings */
   hid_t memtype = H5Tcopy (H5T_C_S1);
+  if (memtype <= 0) return TREXIO_INVALID_ID;
+
   status = H5Tset_size (memtype, H5T_VARIABLE);
+  if (status < 0) return TREXIO_FAILURE;
 
   if ( H5LTfind_dataset(f->$group$_group, $GROUP_DSET$_NAME) != 1 ) {
 
     /* code to create dataset */   
     hid_t filetype = H5Tcopy (H5T_FORTRAN_S1);
+    if (filetype <= 0) return TREXIO_INVALID_ID;
+
     status = H5Tset_size (filetype, H5T_VARIABLE);
+    if (status < 0) return TREXIO_FAILURE;
 
     hid_t dspace = H5Screate_simple( (int) rank, (const hsize_t*) dims, NULL);
+    if (dspace <= 0) return TREXIO_INVALID_ID;
 
     dset_id = H5Dcreate (f->$group$_group, $GROUP_DSET$_NAME, filetype, dspace,
                          H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
diff --git a/src/templates_text/templator_text.org b/src/templates_text/templator_text.org
index aee64ba3..4fcc8920 100644
--- a/src/templates_text/templator_text.org
+++ b/src/templates_text/templator_text.org
@@ -456,8 +456,6 @@ trexio_text_read_$group$ (trexio_text_t* const file)
     }
     // END REPEAT GROUP_DSET_NUM
 
-    size_t tmp_len;
-
     // START REPEAT GROUP_DSET_STR
     /* Allocate arrays */
     $group$->$group_dset$ = CALLOC(size_$group_dset$, $group_dset_dtype$);
@@ -485,7 +483,7 @@ trexio_text_read_$group$ (trexio_text_t* const file)
     ,*/
     char* tmp_$group_dset$;
     if(size_$group_dset$ != 0) tmp_$group_dset$ = CALLOC(size_$group_dset$*32, char);
-    tmp_len = 0;
+    
     for (uint64_t i=0 ; i<size_$group_dset$ ; ++i) {
       $group$->$group_dset$[i] = tmp_$group_dset$;
       /* conventional fcanf with "%s" only return the string before the first space character 
@@ -501,8 +499,8 @@ trexio_text_read_$group$ (trexio_text_t* const file)
 	return NULL;
       }
 
-      tmp_len = strlen($group$->$group_dset$[i]);
-      tmp_$group_dset$ += tmp_len + 1;
+      size_t tmp_$group_dset$_len = strlen($group$->$group_dset$[i]);
+      tmp_$group_dset$ += tmp_$group_dset$_len + 1;
     }
     // END REPEAT GROUP_DSET_STR
 
@@ -852,9 +850,8 @@ trexio_text_write_$group_dset$ (trexio_t* const file, const char** dset, const u
   char* tmp_str = CALLOC(dims[0]*32 + 1, char);
   if (tmp_str == NULL) return TREXIO_ALLOCATION_FAILED;
 
-  size_t tmp_len = 0;
   for (uint64_t i=0 ; i<dims[0] ; ++i) {
-    tmp_len = strlen(dset[i]);
+    size_t tmp_len = strlen(dset[i]);
     $group$->$group_dset$[i] = tmp_str;
     strncpy(tmp_str, dset[i], tmp_len);
     tmp_str += tmp_len + 1; 
diff --git a/tools/check_numpy_i.sh b/tools/check_numpy_i.sh
new file mode 100755
index 00000000..49f56867
--- /dev/null
+++ b/tools/check_numpy_i.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# Check that script is executed from tools directory
+if [[ $(basename $PWD) != "tools" ]] ; then
+  echo "This script should run in the tools directory"
+  exit -1
+fi
+
+TREXIO_ROOT=$(dirname "${PWD}../")
+
+#   First define readonly global variables.
+readonly SRC=${TREXIO_ROOT}/src
+readonly TOOLS=${TREXIO_ROOT}/tools
+
+# We want the script to crash on the 1st error:
+set -e
+
+NUMPY_SRC=${SRC}/numpy.i
+NUMPY_LATEST=${TOOLS}/numpy.i
+
+# Download the latest numpy.i file from NumPy GitHub
+wget https://raw.githubusercontent.com/numpy/numpy/main/tools/swig/numpy.i -O ${NUMPY_LATEST}
+
+# Execute diff to check if the numpy.i file in the src/ directory is updated
+if ! diff -q ${NUMPY_LATEST} ${NUMPY_SRC}  &>/dev/null; then
+  >&2 echo "numpy.i SWIG interface file in the source tree is outdated; replacing ..."
+  mv ${NUMPY_LATEST} ${NUMPY_SRC}
+else
+  >&2 echo "numpy.i SWIG interface file in the source tree is up-to-date."
+  rm ${NUMPY_LATEST}
+fi
+
diff --git a/tools/generator_tools.py b/tools/generator_tools.py
index 7f961753..18c5395d 100644
--- a/tools/generator_tools.py
+++ b/tools/generator_tools.py
@@ -100,7 +100,7 @@ def recursive_populate_file(fname: str, paths: dict, detailed_source: dict) -> N
     fname_new = join('populated',f'pop_{fname}')
     templ_path = get_template_path(fname, paths)
 
-    triggers = ['group_dset_dtype', 'group_dset_h5_dtype', 'default_prec', 'is_index', 
+    triggers = ['group_dset_dtype', 'group_dset_py_dtype', 'group_dset_h5_dtype', 'default_prec', 'is_index', 
                 'group_dset_f_dtype_default', 'group_dset_f_dtype_double', 'group_dset_f_dtype_single', 
                 'group_dset_dtype_default', 'group_dset_dtype_double', 'group_dset_dtype_single', 
                 'group_dset_rank', 'group_dset_dim_list', 'group_dset_f_dims',
@@ -111,25 +111,27 @@ def recursive_populate_file(fname: str, paths: dict, detailed_source: dict) -> N
             with open(join(templ_path,fname_new), 'a') as f_out :
                 num_written = []
                 for line in f_in :
-                        # special case to add error handling for read/write of dimensioning variables
-                        if '$group_dset_dim$' in line:
-                            rc_line = 'if (rc != TREXIO_SUCCESS) return rc;\n'
-                            indentlevel = len(line) - len(line.lstrip())
-                            for dim in detailed_source[item]['dims']:
-                                if not dim.isdigit() and not dim in num_written:
-                                    num_written.append(dim)
-                                    templine = line.replace('$group_dset_dim$', dim)
-                                    if '_read' in templine and (not 'fortran' in fname):
-                                            line_toadd = indentlevel*" " + rc_line
-                                            templine += line_toadd
-
-                                    f_out.write(templine)
-                            num_written = []
-                            continue
-                        # general case of recursive replacement of inline triggers 
-                        else:
-                            populated_line = recursive_replace_line(line, triggers, detailed_source[item])
-                            f_out.write(populated_line)
+                    # special case to add error handling for read/write of dimensioning variables
+                    if '$group_dset_dim$' in line:
+                        rc_line = 'if (rc != TREXIO_SUCCESS) return rc;\n'
+                        indentlevel = len(line) - len(line.lstrip())
+                        for dim in detailed_source[item]['dims']:
+                            if not dim.isdigit() and not dim in num_written:
+                                num_written.append(dim)
+                                templine = line.replace('$group_dset_dim$', dim)
+                                if '_read' in templine and (not 'fortran' in fname):
+                                        line_toadd = indentlevel*" " + rc_line
+                                        templine += line_toadd
+
+                                f_out.write(templine)
+                        num_written = []
+                        continue
+                    # general case of recursive replacement of inline triggers 
+                    else:
+                        populated_line = recursive_replace_line(line, triggers, detailed_source[item])
+                        f_out.write(populated_line)
+
+                f_out.write("\n")
 
 
 def recursive_replace_line (input_line: str, triggers: list, source: dict) -> str:
@@ -214,6 +216,8 @@ def iterative_populate_file (filename: str, paths: dict, groups: dict, datasets:
                     f_out.write(populated_line)
                 else:
                     f_out.write(line)
+                    
+            f_out.write("\n")
 
 
 def iterative_replace_line (input_line: str, case: str, source: dict, add_line: str) -> str:
@@ -403,6 +407,8 @@ def special_populate_text_group(fname: str, paths: dict, group_dict: dict, detai
                     else:
                         loop_body += line
 
+                f_out.write("\n")
+
 
 def get_template_path (filename: str, path_dict: dict) -> str:
     """ 
@@ -542,6 +548,7 @@ def split_dset_dict_detailed (datasets: dict) -> tuple:
             default_prec   = '64'
             group_dset_std_dtype_out = '24.16e'
             group_dset_std_dtype_in = 'lf'
+            group_dset_py_dtype = 'float'
         elif v[0] in ['int', 'index']:
             datatype = 'int64_t'
             group_dset_h5_dtype = 'native_int64'
@@ -554,6 +561,7 @@ def split_dset_dict_detailed (datasets: dict) -> tuple:
             default_prec   = '32'
             group_dset_std_dtype_out = '" PRId64 "'
             group_dset_std_dtype_in  = '" SCNd64 "' 
+            group_dset_py_dtype = 'int'
         elif v[0] == 'str':
             datatype = 'char*'
             group_dset_h5_dtype = ''
@@ -566,6 +574,7 @@ def split_dset_dict_detailed (datasets: dict) -> tuple:
             default_prec   = ''
             group_dset_std_dtype_out = 's'
             group_dset_std_dtype_in  = 's' 
+            group_dset_py_dtype = 'str'
         
         # add the dset name for templates
         tmp_dict['group_dset'] = k
@@ -587,6 +596,7 @@ def split_dset_dict_detailed (datasets: dict) -> tuple:
         tmp_dict['default_prec'] = default_prec
         tmp_dict['group_dset_std_dtype_in'] = group_dset_std_dtype_in
         tmp_dict['group_dset_std_dtype_out'] = group_dset_std_dtype_out
+        tmp_dict['group_dset_py_dtype'] = group_dset_py_dtype
         # add the rank
         tmp_dict['rank'] = len(v[1])
         tmp_dict['group_dset_rank'] = str(tmp_dict['rank'])
diff --git a/tools/prepare_python.sh b/tools/prepare_python.sh
new file mode 100755
index 00000000..be35d362
--- /dev/null
+++ b/tools/prepare_python.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Check that script is executed from tools directory
+if [[ $(basename $PWD) != "tools" ]] ; then
+  echo "This script should run in the tools directory"
+  exit -1
+fi
+
+TREXIO_ROOT=$(dirname "${PWD}../")
+
+#   First define readonly global variables.
+readonly SRC=${TREXIO_ROOT}/src
+readonly INCLUDIR=${TREXIO_ROOT}/include
+readonly TOOLS=${TREXIO_ROOT}/tools
+readonly PYDIR=${TREXIO_ROOT}/python
+readonly PYTREXIODIR=${PYDIR}/pytrexio
+
+# We want the script to crash on the 1st error:
+set -e
+
+# Create src and trexio directories in the python folder if not yet done
+mkdir -p ${PYDIR}/src
+mkdir -p ${PYTREXIODIR}
+
+# Copy all the source code and header files in the corresponding python directory
+cp ${SRC}/pytrexio.py ${PYTREXIODIR}/pytrexio.py
+cp ${SRC}/trexio.py ${PYDIR}/trexio.py
+cp ${SRC}/*.c ${PYDIR}/src
+cp ${SRC}/*.h ${PYDIR}/src
+cp ${INCLUDIR}/trexio.h ${PYDIR}/src 
+
+# Copy additional info
+cp ${TREXIO_ROOT}/AUTHORS ${TREXIO_ROOT}/LICENSE ${PYDIR}
+


Test patch:
diff --git a/python/test/test_api.py b/python/test/test_api.py
new file mode 100644
index 00000000..ae1a1ab6
--- /dev/null
+++ b/python/test/test_api.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+
+import os
+import shutil
+import numpy as np
+
+import trexio
+
+#=========================================================#
+#======== SETUP THE BACK END AND OUTPUT FILE NAME ========#
+#=========================================================#
+
+# 0: TREXIO_HDF5 ; 1: TREXIO_TEXT
+TEST_TREXIO_BACKEND = 0
+OUTPUT_FILENAME_TEXT = 'test_py_swig.dir'
+OUTPUT_FILENAME_HDF5 = 'test_py_swig.h5'
+
+
+# define TREXIO file name
+if TEST_TREXIO_BACKEND == trexio.TREXIO_HDF5:
+    output_filename = OUTPUT_FILENAME_HDF5
+elif TEST_TREXIO_BACKEND == trexio.TREXIO_TEXT:
+    output_filename = OUTPUT_FILENAME_TEXT
+else:
+    raise ValueError ('Specify one of the supported back ends as TEST_TREXIO_BACKEND')
+
+
+# remove TREXIO file if exists in the current directory
+try:
+    if TEST_TREXIO_BACKEND == trexio.TREXIO_HDF5:
+        os.remove(output_filename)
+    elif TEST_TREXIO_BACKEND == trexio.TREXIO_TEXT:
+        shutil.rmtree(output_filename)
+except:
+    print ('Nothing to remove.')
+
+#=========================================================#
+#============ WRITE THE DATA IN THE TEST FILE ============#
+#=========================================================#
+
+# create TREXIO file and open it for writing
+test_file = trexio.File(output_filename, mode='w', back_end=TEST_TREXIO_BACKEND)
+
+# Print docstring of the trexio.open function
+#print(trexio.open.__doc__)
+
+nucleus_num = 12
+
+try:
+    trexio.write_nucleus_num(test_file, -100)
+except trexio.Error:
+    print("Writing negative nucleus_num: checked.")
+
+# write nucleus_num in the file
+try:
+    trexio.write_nucleus_num(test_file, nucleus_num)
+except:
+    raise
+
+try:
+    trexio.write_nucleus_num(test_file, nucleus_num*2)
+except trexio.Error:
+    print("Attempt to overwrite nucleus_num: checked.")
+
+# initialize charge arrays as a list and convert it to numpy array
+charges = [6., 6., 6., 6., 6., 6., 1., 1., 1., 1., 1., 1.]
+#charges_np = np.array(charges, dtype=np.float32)
+charges_np = np.array(charges, dtype=np.int32)
+
+# function call below works with both lists and numpy arrays, dimension needed for memory-safety is derived 
+# from the size of the list/array by SWIG using typemaps from numpy.i
+trexio.write_nucleus_charge(test_file, charges_np)
+
+# initialize arrays of nuclear indices as a list and convert it to numpy array
+indices = [i for i in range(nucleus_num)]
+# type cast is important here because by default numpy transforms a list of integers into int64 array
+indices_np = np.array(indices, dtype=np.int64)
+
+# function call below works with both lists and numpy arrays, dimension needed for memory-safety is derived 
+# from the size of the list/array by SWIG using typemacs from numpy.i
+trexio.write_basis_nucleus_index(test_file, indices_np)
+
+# initialize a list of nuclear coordinates
+coords = [
+ [ 0.00000000 ,  1.39250319 ,  0.00000000 ],
+ [-1.20594314 ,  0.69625160 ,  0.00000000 ],
+ [-1.20594314 , -0.69625160 ,  0.00000000 ],
+ [ 0.00000000 , -1.39250319 ,  0.00000000 ],
+ [ 1.20594314 , -0.69625160 ,  0.00000000 ],
+ [ 1.20594314 ,  0.69625160 ,  0.00000000 ],
+ [-2.14171677 ,  1.23652075 ,  0.00000000 ],
+ [-2.14171677 , -1.23652075 ,  0.00000000 ],
+ [ 0.00000000 , -2.47304151 ,  0.00000000 ],
+ [ 2.14171677 , -1.23652075 ,  0.00000000 ],
+ [ 2.14171677 ,  1.23652075 ,  0.00000000 ],
+ [ 0.00000000 ,  2.47304151 ,  0.00000000 ],
+  ]
+
+# write coordinates in the file
+trexio.write_nucleus_coord(test_file, coords)
+
+point_group = 'B3U'
+
+# write nucleus_point_group in the file
+trexio.write_nucleus_point_group(test_file, point_group)
+
+labels = [
+        'C',
+        'C',
+        'C',
+        'C',
+        'C',
+        'C',
+        'H',
+        'H',
+        'H',
+        'H',
+        'H',
+        'H']
+
+# write nucleus_label in the file
+trexio.write_nucleus_label(test_file,labels)
+
+# close TREXIO file 
+# this call is no longer needed as we introduced TREXIO_File class which has a desctructor that closes the file
+#trexio.close(test_file)
+# without calling destructor on test_file the TREXIO_FILE is not getting created and the data is not written when using TEXT back end. 
+# This, the user still has to explicitly call destructor on test_file object instead of the trexio.close function. 
+# This is only an issue when the data is getting written and read in the same session (e.g. in Jupyter notebook)
+del test_file
+
+
+#==========================================================#
+#============ READ THE DATA FROM THE TEST FILE ============#
+#==========================================================#
+
+# open previously created TREXIO file, now in 'read' mode
+test_file2 = trexio.File(output_filename, 'r', TEST_TREXIO_BACKEND)
+
+# check for existence of some of the previously written variables
+assert trexio.has_nucleus_num
+assert trexio.has_nucleus_charge
+assert trexio.has_nucleus_coord
+assert trexio.has_nucleus_label
+assert trexio.has_nucleus_point_group
+
+# read nucleus_num from file
+rnum = trexio.read_nucleus_num(test_file2)
+assert rnum==nucleus_num
+
+# safe call to read_nucleus_charge array of float values
+rcharges_np = trexio.read_nucleus_charge(test_file2, dim=nucleus_num)
+assert rcharges_np.dtype is np.dtype(np.float64)
+np.testing.assert_array_almost_equal(rcharges_np, charges_np, decimal=8)
+
+# unsafe call to read_safe should fail with error message corresponding to TREXIO_UNSAFE_ARRAY_DIM
+try:
+    rcharges_fail = trexio.read_nucleus_charge(test_file2, dim=nucleus_num*5)
+except trexio.Error:
+    print("Unsafe call to safe API: checked")
+
+# safe call to read array of int values (nuclear indices)
+rindices_np_16 = trexio.read_basis_nucleus_index(test_file2, dim=nucleus_num, dtype=np.int16)
+assert rindices_np_16.dtype is np.dtype(np.int16)
+for i in range(nucleus_num):
+    assert rindices_np_16[i]==indices_np[i]
+
+rindices_np_32 = trexio.read_basis_nucleus_index(test_file2, dim=nucleus_num, dtype=np.int32)
+assert rindices_np_32.dtype is np.dtype(np.int32)
+for i in range(nucleus_num):
+    assert rindices_np_32[i]==indices_np[i]
+
+rindices_np_64 = trexio.read_basis_nucleus_index(test_file2)
+assert rindices_np_64.dtype is np.dtype(np.int64)
+assert rindices_np_64.size==nucleus_num
+for i in range(nucleus_num):
+    assert rindices_np_64[i]==indices_np[i]
+
+# read nuclear coordinates without providing optional argument dim
+rcoords_np = trexio.read_nucleus_coord(test_file2)
+
+assert rcoords_np.size==nucleus_num*3
+np.testing.assert_array_almost_equal(rcoords_np, np.array(coords).reshape(nucleus_num,3), decimal=8)
+
+# set doReshape to False to get a flat 1D array (e.g. when reading matrices like nuclear coordinates)
+#rcoords_reshaped_2 = trexio.read_nucleus_coord(test_file2, doReshape=False)
+
+# read array of nuclear labels
+rlabels_2d = trexio.read_nucleus_label(test_file2, dim=nucleus_num)
+print(rlabels_2d)
+for i in range(nucleus_num):
+    assert rlabels_2d[i]==labels[i]
+
+# read a string corresponding to nuclear point group
+rpoint_group = trexio.read_nucleus_point_group(test_file2)
+assert rpoint_group==point_group 
+
+# another way to read only if the variable exists
+if trexio.has_mo_num(test_file2):
+    rmo_num = trexio.read_mo_num(test_file2)
+else:
+    print("Not reading the non-existing variable mo_num.")
+
+# close TREXIO file
+#trexio.close(test_file2)
+
+# cleaning (remove the TREXIO file)
+try:
+    if TEST_TREXIO_BACKEND == trexio.TREXIO_HDF5:
+        os.remove(output_filename)
+    elif TEST_TREXIO_BACKEND == trexio.TREXIO_TEXT:
+        shutil.rmtree(output_filename)
+except:
+    print (f'No output file {output_filename} has been produced')
+
+#==========================================================#
+
diff --git a/python/test/test_pytrexio.py b/python/test/test_pytrexio.py
new file mode 100644
index 00000000..04b1266c
--- /dev/null
+++ b/python/test/test_pytrexio.py
@@ -0,0 +1,160 @@
+import os
+import shutil
+import numpy as np
+
+from pytrexio.pytrexio import *
+
+#=========================================================#
+#======== SETUP THE BACK END AND OUTPUT FILE NAME ========#
+#=========================================================#
+
+# 0: TREXIO_HDF5 ; 1: TREXIO_TEXT
+TEST_TREXIO_BACKEND = 1
+OUTPUT_FILENAME_TEXT = 'test_py_swig.dir'
+OUTPUT_FILENAME_HDF5 = 'test_py_swig.h5'
+
+
+if TEST_TREXIO_BACKEND == 0:
+    output_filename = OUTPUT_FILENAME_HDF5
+elif TEST_TREXIO_BACKEND == 1:
+    output_filename = OUTPUT_FILENAME_TEXT
+else:
+    raise ValueError ('Specify one of the supported back ends as TEST_TREXIO_BACKEND')
+
+
+try:
+    if TEST_TREXIO_BACKEND == 0:
+        os.remove(output_filename)
+    elif TEST_TREXIO_BACKEND == 1:
+        shutil.rmtree(output_filename)
+except:
+    print ('Nothing to remove.')
+
+#=========================================================#
+#============ WRITE THE DATA IN THE TEST FILE ============#
+#=========================================================#
+
+test_file = trexio_open(output_filename, 'w', TEST_TREXIO_BACKEND)
+
+nucleus_num = 12
+
+rc = trexio_write_nucleus_num(test_file, nucleus_num)
+assert rc==0
+
+# initialize charge arrays as a list and convert it to numpy array
+charges = [6., 6., 6., 6., 6., 6., 1., 1., 1., 1., 1., 1.]
+charges_np = np.array(charges, dtype=np.float64)
+
+# function call below works with both lists and numpy arrays, dimension needed for memory-safety is derived 
+# from the size of the list/array by SWIG using typemaps from numpy.i
+rc = trexio_write_safe_nucleus_charge(test_file, charges_np)
+assert rc==0
+
+# less Python-ic way to read/write arrays using Array classes (probably more portable to other languages)
+#charges = doubleArray(nucleus_num)
+#for i in range(nucleus_num):
+#    if i < nucleus_num/2:
+#        charges[i] = 6.
+#    else:
+#        charges[i] = 1.
+#rc = trexio_write_nucleus_charge(test_file, charges)
+
+# initialize arrays of nuclear indices as a list and convert it to numpy array
+indices = [i for i in range(nucleus_num)]
+# type cast is important here because by default numpy transforms a list of integers into int64 array
+indices_np = np.array(indices, dtype=np.int32)
+
+# function call below works with both lists and numpy arrays, dimension needed for memory-safety is derived 
+# from the size of the list/array by SWIG using typemacs from numpy.i
+rc = trexio_write_safe_basis_nucleus_index(test_file, indices_np)
+assert rc==0
+
+point_group = 'B3U'
+
+rc = trexio_write_nucleus_point_group(test_file, point_group, 10)
+assert rc==0
+
+labels = [
+        'C',
+        'C',
+        'C',
+        'C',
+        'C',
+        'C',
+        'H',
+        'H',
+        'H',
+        'H',
+        'H',
+        'H']
+
+rc = trexio_write_nucleus_label(test_file, labels, 10)
+assert rc==0
+
+rc = trexio_close(test_file)
+assert rc==0
+
+#==========================================================#
+#============ READ THE DATA FROM THE TEST FILE ============#
+#==========================================================#
+
+test_file2 = trexio_open(output_filename, 'r', TEST_TREXIO_BACKEND)
+
+result = trexio_read_nucleus_num(test_file2)
+assert result[0]==0
+assert result[1]==nucleus_num
+
+# safe call to read_safe array of float values
+rc, rcharges_np = trexio_read_safe_nucleus_charge(test_file2, nucleus_num)
+assert rc==0
+assert rcharges_np.dtype is np.dtype(np.float64)
+np.testing.assert_array_almost_equal(rcharges_np, charges_np, decimal=8)
+
+# unsafe call to read_safe should not only have return code = TREXIO_UNSAFE_ARRAY_DIM
+# TODO: it should not return numpy array filled with garbage
+rc, rcharges_fail = trexio_read_safe_nucleus_charge(test_file2, nucleus_num*5)
+assert rc==23
+
+# less Python-ic way to read/write arrays using Array classes (probably more portable to other languages)
+#charges2 = doubleArray(nucleus_num)
+#for i in range(nucleus_num):
+#    charges2[i] = -1.
+#rc = trexio_read_nucleus_charge(test_file2, charges2)
+#assert rc==0
+#for i in range(nucleus_num):
+#    assert charges2[i]==charges[i]
+
+# safe call to read_safe array of int values
+rc, rindices_np = trexio_read_safe_basis_nucleus_index(test_file2, nucleus_num)
+assert rc==0
+assert rindices_np.dtype is np.dtype(np.int32)
+for i in range(nucleus_num):
+    assert rindices_np[i]==indices_np[i]
+
+# currently only low-level routines (return one long string instead of an array of strings) work
+rc, labels_1d = trexio_read_nucleus_label_low(test_file2, 10)
+assert rc==0
+
+labels_2d = [label for label in labels_1d.split(TREXIO_DELIM) if label]
+print('Read and parsed nuclear labels:\n', labels_2d)
+for i in range(nucleus_num):
+    assert labels_2d[i]==labels[i]
+
+rc, rpoint_group = trexio_read_nucleus_point_group(test_file2, 3)
+print(f'Read point group: {rpoint_group}')
+assert rc==0
+assert rpoint_group==point_group
+
+rc = trexio_close(test_file2)
+assert rc==0
+
+try:
+    if TEST_TREXIO_BACKEND == 0:
+        os.remove(output_filename)
+    elif TEST_TREXIO_BACKEND == 1:
+        shutil.rmtree(output_filename)
+except:
+    print (f'No output file {output_filename} has been produced')
+
+#==========================================================#
+
diff --git a/tests/io_safe_dset_float_hdf5.c b/tests/io_safe_dset_float_hdf5.c
new file mode 100644
index 00000000..f70fad77
--- /dev/null
+++ b/tests/io_safe_dset_float_hdf5.c
@@ -0,0 +1,165 @@
+#include "trexio.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#define TEST_BACKEND 	TREXIO_HDF5
+#define TREXIO_FILE 	"test_safe_dset_f.h5"
+#define RM_COMMAND 	"rm -rf " TREXIO_FILE
+
+static int test_write_dset (const char* file_name, const back_end_t backend) {
+
+/* Try to write a dataset with floating point values into the TREXIO file using safe API */
+
+  trexio_t* file = NULL;
+  trexio_exit_code rc;
+
+  // parameters to be written
+  int num = 12;
+  double coord[36] = {
+  0.00000000 ,  1.39250319 ,  0.00000000 ,
+ -1.20594314 ,  0.69625160 ,  0.00000000 ,
+ -1.20594314 , -0.69625160 ,  0.00000000 ,
+  0.00000000 , -1.39250319 ,  0.00000000 ,
+  1.20594314 , -0.69625160 ,  0.00000000 ,
+  1.20594314 ,  0.69625160 ,  0.00000000 ,
+ -2.14171677 ,  1.23652075 ,  0.00000000 ,
+ -2.14171677 , -1.23652075 ,  0.00000000 ,
+  0.00000000 , -2.47304151 ,  0.00000000 ,
+  2.14171677 , -1.23652075 ,  0.00000000 ,
+  2.14171677 ,  1.23652075 ,  0.00000000 ,
+  0.00000000 ,  2.47304151 ,  0.00000000 ,
+  };
+
+/*================= START OF TEST ==================*/
+
+  // open file in 'write' mode
+  file = trexio_open(file_name, 'w', backend);
+  assert (file != NULL);
+
+  // write numerical attribute in an empty file
+  rc = trexio_write_nucleus_num(file, num);
+  assert (rc == TREXIO_SUCCESS);
+  
+  /* write numerical dataset with an unsafe dimension
+   * this should return TREXIO_UNSAFE_ARRAY_DIM indicating
+   * that access beyong allocated memory is likely to occur */
+  uint64_t dim_unsafe = num * 12;
+  rc = trexio_write_safe_nucleus_coord(file, coord, dim_unsafe);
+  assert (rc == TREXIO_UNSAFE_ARRAY_DIM);
+
+  /* write numerical dataset with a safe dimension
+   * this should return TREXIO_SUCCESS */
+  uint64_t dim_safe = num * 3;
+  rc = trexio_write_safe_nucleus_coord(file, coord, dim_safe);
+  assert (rc == TREXIO_SUCCESS);
+
+  // close current session
+  rc = trexio_close(file);
+  assert (rc == TREXIO_SUCCESS);
+
+/*================= END OF TEST ==================*/
+
+  return 0;
+}
+
+
+static int test_has_dset (const char* file_name, const back_end_t backend) {
+
+/* Try to check the existence of a dataset in the TREXIO file */
+
+  trexio_t* file = NULL;
+  trexio_exit_code rc;
+
+/*================= START OF TEST ==================*/
+
+  // open file in 'read' mode
+  file = trexio_open(file_name, 'r', backend);
+  assert (file != NULL);
+
+  // check that the previously written dataset exists
+  rc = trexio_has_nucleus_coord(file);
+  assert (rc == TREXIO_SUCCESS);
+
+  // close current session
+  rc = trexio_close(file);
+  assert (rc == TREXIO_SUCCESS);
+
+/*================= END OF TEST ==================*/
+
+  return 0;
+}
+
+
+static int test_read_dset (const char* file_name, const back_end_t backend) {
+
+/* Try to read a dataset with floating point values from the TREXIO file using safe API */
+
+  trexio_t* file = NULL;
+  trexio_exit_code rc;
+
+  // parameters to be read
+  int num;
+  double* coord;
+
+/*================= START OF TEST ==================*/
+
+  // open file in 'read' mode
+  file = trexio_open(file_name, 'r', backend);
+  assert (file != NULL);
+
+  // read numerical attribute from the file
+  rc = trexio_read_nucleus_num(file, &num);
+  assert (rc == TREXIO_SUCCESS);
+  assert (num == 12);
+
+  // read numerical (floating point) dataset from the file
+  coord = (double*) calloc(3*num, sizeof(double));
+
+  /* write numerical dataset with an unsafe dimension
+   * this should return TREXIO_UNSAFE_ARRAY_DIM indicating
+   * that access beyong allocated memory is likely to occur */
+  uint64_t dim_unsafe = num * 12;
+  rc = trexio_read_safe_nucleus_coord(file, coord, dim_unsafe);
+  assert (rc == TREXIO_UNSAFE_ARRAY_DIM);
+
+  /* write numerical dataset with a safe dimension
+   * this should return TREXIO_SUCCESS */
+  uint64_t dim_safe = num * 3;
+  rc = trexio_read_safe_nucleus_coord(file, coord, dim_safe);
+  assert (rc == TREXIO_SUCCESS);
+
+  double x = coord[30] - 2.14171677;
+  assert( x*x < 1.e-14 );
+  free(coord);
+
+  // close current session
+  rc = trexio_close(file);
+  assert (rc == TREXIO_SUCCESS);
+
+/*================= END OF TEST ==================*/
+
+  return 0;
+}
+
+
+int main(void) {
+
+/*============== Test launcher ================*/
+
+  int rc;
+  rc = system(RM_COMMAND);
+  assert (rc == 0);
+
+  test_write_dset (TREXIO_FILE, TEST_BACKEND);
+  test_has_dset   (TREXIO_FILE, TEST_BACKEND);
+  test_read_dset  (TREXIO_FILE, TEST_BACKEND);
+
+  rc = system(RM_COMMAND);
+  assert (rc == 0);
+
+  return 0;
+}
+
+
diff --git a/tests/io_safe_dset_float_text.c b/tests/io_safe_dset_float_text.c
new file mode 100644
index 00000000..709abd09
--- /dev/null
+++ b/tests/io_safe_dset_float_text.c
@@ -0,0 +1,165 @@
+#include "trexio.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#define TEST_BACKEND 	TREXIO_TEXT
+#define TREXIO_FILE 	"test_safe_dset_f.dir"
+#define RM_COMMAND 	"rm -rf " TREXIO_FILE
+
+static int test_write_dset (const char* file_name, const back_end_t backend) {
+
+/* Try to write a dataset with floating point values into the TREXIO file using safe API */
+
+  trexio_t* file = NULL;
+  trexio_exit_code rc;
+
+  // parameters to be written
+  int num = 12;
+  double coord[36] = {
+  0.00000000 ,  1.39250319 ,  0.00000000 ,
+ -1.20594314 ,  0.69625160 ,  0.00000000 ,
+ -1.20594314 , -0.69625160 ,  0.00000000 ,
+  0.00000000 , -1.39250319 ,  0.00000000 ,
+  1.20594314 , -0.69625160 ,  0.00000000 ,
+  1.20594314 ,  0.69625160 ,  0.00000000 ,
+ -2.14171677 ,  1.23652075 ,  0.00000000 ,
+ -2.14171677 , -1.23652075 ,  0.00000000 ,
+  0.00000000 , -2.47304151 ,  0.00000000 ,
+  2.14171677 , -1.23652075 ,  0.00000000 ,
+  2.14171677 ,  1.23652075 ,  0.00000000 ,
+  0.00000000 ,  2.47304151 ,  0.00000000 ,
+  };
+
+/*================= START OF TEST ==================*/
+
+  // open file in 'write' mode
+  file = trexio_open(file_name, 'w', backend);
+  assert (file != NULL);
+
+  // write numerical attribute in an empty file
+  rc = trexio_write_nucleus_num(file, num);
+  assert (rc == TREXIO_SUCCESS);
+  
+  /* write numerical dataset with an unsafe dimension
+   * this should return TREXIO_UNSAFE_ARRAY_DIM indicating
+   * that access beyong allocated memory is likely to occur */
+  uint64_t dim_unsafe = num * 12;
+  rc = trexio_write_safe_nucleus_coord(file, coord, dim_unsafe);
+  assert (rc == TREXIO_UNSAFE_ARRAY_DIM);
+
+  /* write numerical dataset with a safe dimension
+   * this should return TREXIO_SUCCESS */
+  uint64_t dim_safe = num * 3;
+  rc = trexio_write_safe_nucleus_coord(file, coord, dim_safe);
+  assert (rc == TREXIO_SUCCESS);
+
+  // close current session
+  rc = trexio_close(file);
+  assert (rc == TREXIO_SUCCESS);
+
+/*================= END OF TEST ==================*/
+
+  return 0;
+}
+
+
+static int test_has_dset (const char* file_name, const back_end_t backend) {
+
+/* Try to check the existence of a dataset in the TREXIO file */
+
+  trexio_t* file = NULL;
+  trexio_exit_code rc;
+
+/*================= START OF TEST ==================*/
+
+  // open file in 'read' mode
+  file = trexio_open(file_name, 'r', backend);
+  assert (file != NULL);
+
+  // check that the previously written dataset exists
+  rc = trexio_has_nucleus_coord(file);
+  assert (rc == TREXIO_SUCCESS);
+
+  // close current session
+  rc = trexio_close(file);
+  assert (rc == TREXIO_SUCCESS);
+
+/*================= END OF TEST ==================*/
+
+  return 0;
+}
+
+
+static int test_read_dset (const char* file_name, const back_end_t backend) {
+
+/* Try to read a dataset with floating point values from the TREXIO file using safe API */
+
+  trexio_t* file = NULL;
+  trexio_exit_code rc;
+
+  // parameters to be read
+  int num;
+  double* coord;
+
+/*================= START OF TEST ==================*/
+
+  // open file in 'read' mode
+  file = trexio_open(file_name, 'r', backend);
+  assert (file != NULL);
+
+  // read numerical attribute from the file
+  rc = trexio_read_nucleus_num(file, &num);
+  assert (rc == TREXIO_SUCCESS);
+  assert (num == 12);
+
+  // read numerical (floating point) dataset from the file
+  coord = (double*) calloc(3*num, sizeof(double));
+
+  /* write numerical dataset with an unsafe dimension
+   * this should return TREXIO_UNSAFE_ARRAY_DIM indicating
+   * that access beyong allocated memory is likely to occur */
+  uint64_t dim_unsafe = num * 12;
+  rc = trexio_read_safe_nucleus_coord(file, coord, dim_unsafe);
+  assert (rc == TREXIO_UNSAFE_ARRAY_DIM);
+
+  /* write numerical dataset with a safe dimension
+   * this should return TREXIO_SUCCESS */
+  uint64_t dim_safe = num * 3;
+  rc = trexio_read_safe_nucleus_coord(file, coord, dim_safe);
+  assert (rc == TREXIO_SUCCESS);
+
+  double x = coord[30] - 2.14171677;
+  assert( x*x < 1.e-14 );
+  free(coord);
+
+  // close current session
+  rc = trexio_close(file);
+  assert (rc == TREXIO_SUCCESS);
+
+/*================= END OF TEST ==================*/
+
+  return 0;
+}
+
+
+int main(void) {
+
+/*============== Test launcher ================*/
+
+  int rc;
+  rc = system(RM_COMMAND);
+  assert (rc == 0);
+
+  test_write_dset (TREXIO_FILE, TEST_BACKEND);
+  test_has_dset   (TREXIO_FILE, TEST_BACKEND);
+  test_read_dset  (TREXIO_FILE, TEST_BACKEND);
+
+  rc = system(RM_COMMAND);
+  assert (rc == 0);
+
+  return 0;
+}
+
+