//- ****************************************************************************
//-
//- Copyright 2009 National Technology & Engineering Solutions of Sandia, LLC
//- (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
//- Government retains certain rights in this software.
//-
//- BSD Open Source License
//- All rights reserved.
//-
//- Redistribution and use in source and binary forms, with or without
//- modification, are permitted provided that the following conditions are met:
//-
//-   1. Redistributions of source code must retain the above copyright notice,
//-      this list of conditions and the following disclaimer.
//-
//-   2. 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.
//-
//-   3. Neither the name of the copyright holder nor the names of its
//-      contributors may be used to endorse or promote products derived from
//-      this software without specific prior written permission.
//-
//- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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.
//-
//- ****************************************************************************

// include statements
#include <pybind11/pybind11.h>   // c++ bindings for python
#include <pybind11/stl.h>        // converting between c++ and python types
#include "slbm_Py_shell.h"       // header file for this program

// some shorthand, by convention
namespace py = pybind11;

// Python module declaration
PYBIND11_MODULE(libslbmPyshell, m)
{

    //-----------------------------------------
    // exception handling
    //-----------------------------------------

    py::register_exception_translator([](std::exception_ptr p) {
        try
        {
            if (p) std::rethrow_exception(p);
        }
        catch (SLBMException &e)
        {
            PyErr_SetString(PyExc_RuntimeError, e.emessage.c_str());
        }
        catch (GeoTessException &e)
        {
            PyErr_SetString(PyExc_RuntimeError, e.emessage.c_str());
        }
        catch (...)
        {
            PyErr_SetString(PyExc_RuntimeError, "An unknown error has occurred.");
        }
    });


    //-----------------------------------------
    // SLBM and GeoTess globals
    //-----------------------------------------

    // version
    m.attr("SlbmVersion") = SlbmVersion;

    // p and s-wave indices
    m.attr("PWAVE") = PWAVE;
    m.attr("SWAVE") = SWAVE;

    // phases
    m.attr("Pn") = Pn;
    m.attr("Sn") = Sn;
    m.attr("Pg") = Pg;
    m.attr("Lg") = Lg;

    // attributes
    m.attr("TT") = TT;
    m.attr("SH") = SH;
    m.attr("AZ") = AZ;

    // RSTT layers
    m.attr("WATER")          = WATER;
    m.attr("SEDIMENT1")      = SEDIMENT1;
    m.attr("SEDIMENT2")      = SEDIMENT2;
    m.attr("SEDIMENT3")      = SEDIMENT3;
    m.attr("UPPER_CRUST")    = UPPER_CRUST;
    m.attr("MIDDLE_CRUST_N") = MIDDLE_CRUST_N;
    m.attr("MIDDLE_CRUST_G") = MIDDLE_CRUST_G;
    m.attr("LOWER_CRUST")    = LOWER_CRUST;
    m.attr("MANTLE")         = MANTLE;
    m.attr("NLAYERS")        = NLAYERS;

    // earth constants
    m.attr("EARTH_RAD") = EARTH_RAD;  // kilometers


    //-----------------------------------------
    // SlbmInterface
    //-----------------------------------------

    py::class_<SlbmInterface>(m, "SlbmInterface")  // declare SlbmInterface class


        // constructors
        //----------------------------------------

        .def(py::init<>())        // void
        .def(py::init<double>())  // string


        // operators
        //----------------------------------------
        .def("__eq__", &SlbmInterface::operator==)
        .def("__ne__", &SlbmInterface::operator!=)


        // attributes
        //----------------------------------------
        .def_property_readonly("srcLat", &SlbmInterface::getSrcLat)  // getter
        .def_property_readonly("srcLon", &SlbmInterface::getSrcLon)  // getter
        .def_property_readonly("srcDep", &SlbmInterface::getSrcDep)  // getter
        .def_property_readonly("rcvLat", &SlbmInterface::getRcvLat)  // getter
        .def_property_readonly("rcvLon", &SlbmInterface::getRcvLon)  // getter
        .def_property_readonly("rcvDep", &SlbmInterface::getRcvDep)  // getter
        .def_property_readonly("valid",  &SlbmInterface::isValid)  // getter

        .def_property_static("CH_MAX",  // different notation for static attributes
            [](py::object) { return SlbmInterface::getCHMax(); },            // getter
            [](py::object, const double x) { SlbmInterface::setCHMax(x); })  // setter
        // .def_property("CH_MAX", [](SlbmInterface &self) { return self.getCHMax(); },  // getter
        //     [](SlbmInterface &self, const double x) { self.setCHMax(x); })            // setter


        // simple methods
        //----------------------------------------------------------------------
        // they either have no return, or they return something via a `return`
        // call. PyBind takes care of all the type conversions)

        .def("getVersion",               &SlbmInterface::getVersion)
        .def("loadVelocityModel",        &SlbmInterface::loadVelocityModel)
        .def("saveVelocityModel",        &SlbmInterface::saveVelocityModel)
        .def("getBufferSize",            &SlbmInterface::getBufferSize)
        .def("setInterpolatorType",      &SlbmInterface::setInterpolatorType)
        .def("getInterpolatorType",      &SlbmInterface::getInterpolatorType)
        .def("clear",                    &SlbmInterface::clear)
        .def("isValid",                  &SlbmInterface::isValid)
        .def("getPhase",                 &SlbmInterface::getPhase)
        .def("toString",                 &SlbmInterface::toString)
        .def("getNActiveNodes",          &SlbmInterface::getNActiveNodes)
        .def("clearActiveNodes",         &SlbmInterface::clearActiveNodes)
        .def("getGridNodeId",            &SlbmInterface::getGridNodeId)
        .def("getActiveNodeId",          &SlbmInterface::getActiveNodeId)
        .def("clearNodeHitCount",        &SlbmInterface::clearNodeHitCount)
        .def("getUncertaintyTable",      &SlbmInterface::getUncertaintyTable)
        .def("getUncertaintyFileFormat", &SlbmInterface::getUncertaintyFileFormat)
        .def("getClassCount",            &SlbmInterface::getClassCount)
        .def("getModelPath",             &SlbmInterface::getModelPath)
        .def("setDelDistance",           &SlbmInterface::setDelDistance)
        .def("setDelDepth",              &SlbmInterface::setDelDepth)
        .def("setPathIncrement",         &SlbmInterface::setPathIncrement)
        .def("getModelString",           &SlbmInterface::getModelString)
        .def("isEqual",                  &SlbmInterface::isEqual)
        .def_static("modelsEqual",       &SlbmInterface::modelsEqual)


        // simple overloaded methods
        //----------------------------------------------------------------------
        // we can still let PyBind figure out the type conversions, but if a
        // function is overloaded, at a minimum, we have to explicitly state
        // its return-type and inputs so it knows where to look

        .def("getDistance",              (double (SlbmInterface::*)()) &SlbmInterface::getDistance)
        .def("getSourceDistance",        (double (SlbmInterface::*)()) &SlbmInterface::getSourceDistance)
        .def("getReceiverDistance",      (double (SlbmInterface::*)()) &SlbmInterface::getReceiverDistance)
        .def("getHeadwaveDistance",      (double (SlbmInterface::*)()) &SlbmInterface::getHeadwaveDistance)
        .def("getHeadwaveDistanceKm",    (double (SlbmInterface::*)()) &SlbmInterface::getHeadwaveDistanceKm)
        .def("getTravelTime",            (double (SlbmInterface::*)()) &SlbmInterface::getTravelTime)
        .def("getSlowness",              (double (SlbmInterface::*)()) &SlbmInterface::getSlowness)
        .def("get_dtt_ddist",            (double (SlbmInterface::*)()) &SlbmInterface::get_dtt_ddist)
        .def("get_dtt_dlat",             (double (SlbmInterface::*)()) &SlbmInterface::get_dtt_dlat)
        .def("get_dtt_dlon",             (double (SlbmInterface::*)()) &SlbmInterface::get_dtt_dlon)
        .def("get_dtt_ddepth",           (double (SlbmInterface::*)()) &SlbmInterface::get_dtt_ddepth)
        .def("getNGridNodes",            (int    (SlbmInterface::*)()) &SlbmInterface::getNGridNodes)
        .def("getNHeadWavePoints",       (int    (SlbmInterface::*)()) &SlbmInterface::getNHeadWavePoints)
        .def("getTessId",                (string (SlbmInterface::*)()) &SlbmInterface::getTessId)
        .def("getFractionActive",        (double (SlbmInterface::*)()) &SlbmInterface::getFractionActive)
        .def("getDelDistance",           (double (SlbmInterface::*)()) &SlbmInterface::getDelDistance)
        .def("getDelDepth",              (double (SlbmInterface::*)()) &SlbmInterface::getDelDepth)
        .def("getRayParameter",          (double (SlbmInterface::*)()) &SlbmInterface::getRayParameter)
        .def("getTurningRadius",         (double (SlbmInterface::*)()) &SlbmInterface::getTurningRadius)
        .def("getPathIncrement",         (double (SlbmInterface::*)()) &SlbmInterface::getPathIncrement)

        .def("getNodeHitCount",          (int    (SlbmInterface::*)(const int &)) &SlbmInterface::getNodeHitCount)
        .def("getAverageMantleVelocity", (double (SlbmInterface::*)(const int &)) &SlbmInterface::getAverageMantleVelocity)

        .def("initializeActiveNodes", (void (SlbmInterface::*)(
            const string &)) &SlbmInterface::initializeActiveNodes)
        
        .def("initializeActiveNodes", (void (SlbmInterface::*)(
            const double &, const double &, const double &, const double &)) &SlbmInterface::initializeActiveNodes)

        .def("initializeActiveNodes", (void (SlbmInterface::*)(
            const vector<double> lat, const vector<double> lon, const bool &)) &SlbmInterface::initializeActiveNodes)

        .def("createGreatCircle", (void (SlbmInterface::*)(
            const string &,
            const double &, const double &, const double &,
            const double &, const double &, const double &)
            ) &SlbmInterface::createGreatCircle)
        .def("createGreatCircle", (void (SlbmInterface::*)(
            const int &,
            const double &, const double &, const double &,
            const double &, const double &, const double &)
            ) &SlbmInterface::createGreatCircle)


        // return-by-reference methods
        //----------------------------------------------------------------------
        // Python doesn't allow passing references and pointers around, so we
        // have to write lambda functions that initialize variables in
        // memory where necessary, send the right inputs to the method, and
        // return the applicable values

        .def("getTravelTimeComponents", [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double tTotal, tSource, tReceiver, tHeadwave, tGradient;

                // call method, store results into the variables in memory
                self.getTravelTimeComponents(tTotal, tSource, tReceiver, tHeadwave, tGradient);

                // return a tuple of the values
                return py::make_tuple(tTotal, tSource, tReceiver, tHeadwave, tGradient);
            })

        .def("getTravelTimeComponents",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double tTotal, tSource, tReceiver, tHeadwave, tGradient;

                // call method, store results into the variables in memory
                self.getTravelTimeComponents(tTotal, tSource, tReceiver, tHeadwave, tGradient);

                // return a tuple of the values
                return py::make_tuple(tTotal, tSource, tReceiver, tHeadwave, tGradient);
            })

        .def("getWeightsSource",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeids;
                vector<double> weights;

                // call method, store results into the variables in memory
                self.getWeightsSource(nodeids, weights);

                // return a tuple of the values
                return py::make_tuple(nodeids, weights);
            })

        .def("getActiveNodeWeightsSource",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeids;
                vector<double> weights;

                // call method, store results into the variables in memory
                self.getActiveNodeWeightsSource(nodeids, weights);

                // return a tuple of the values
                return py::make_tuple(nodeids, weights);
            })

        .def("getWeightsReceiver",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeids;
                vector<double> weights;

                // call method, store results into the variables in memory
                self.getWeightsReceiver(nodeids, weights);

                // return a tuple of the values
                return py::make_tuple(nodeids, weights);
            })

        .def("getActiveNodeWeightsReceiver",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeids;
                vector<double> weights;

                // call method, store results into the variables in memory
                self.getActiveNodeWeightsReceiver(nodeids, weights);

                // return a tuple of the values
                return py::make_tuple(nodeids, weights);
            })

        .def("getGridData",
            [](SlbmInterface &self, const int nodeId)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double latitude, longitude;
                vector<double> depth(NLAYERS), pvelocity(NLAYERS), svelocity(NLAYERS), gradient(2);

                // call method, store results into the variables in memory
                // (using vector.data() trick, here, so we can use vectors in place
                //  of arrays because vectors more easily convert to Python tuples)
                self.getGridData(nodeId, latitude, longitude,
                    depth.data(), pvelocity.data(), svelocity.data(), gradient.data());

                // return a tuple of the values
                return py::make_tuple(latitude, longitude, depth, pvelocity, svelocity, gradient);
            })

        .def("getActiveNodeData",
            [](SlbmInterface &self, const int nodeId)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double latitude, longitude;
                vector<double> depth(NLAYERS), pvelocity(NLAYERS), svelocity(NLAYERS), gradient(2);

                // call method, store results into the variables in memory
                // (using vector.data() trick, here, so we can use vectors in place
                //  of arrays because vectors more easily convert to Python tuples)
                self.getActiveNodeData(nodeId, latitude, longitude,
                    depth.data(), pvelocity.data(), svelocity.data(), gradient.data());

                // return a tuple of the values
                return py::make_tuple(latitude, longitude, depth, pvelocity, svelocity, gradient);
            })

        .def("getGreatCircleData",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                string phase;
                double actual_path_increment;
                int npoints;
                vector<double> sourceDepth, sourceVelocity, receiverDepth,
                               receiverVelocity, headWaveVelocity, gradient;

                // call method, store results into the variables in memory
                // (using vector.data() trick, here, so we can use vectors in place
                //  of arrays because vectors more easily convert to Python tuples)
                self.getGreatCircleData(phase, actual_path_increment,
                    sourceDepth, sourceVelocity, receiverDepth,
                    receiverVelocity, npoints, headWaveVelocity, gradient);

                // return a tuple of the values
                return py::make_tuple(phase, actual_path_increment,
                    sourceDepth, sourceVelocity, receiverDepth, receiverVelocity,
                    npoints, headWaveVelocity, gradient);

            })

        .def("getGreatCircleLocations",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<double> lat, lon, depth;

                // call method, store results into the variables in memory
                // (using vector.data() trick, here, so we can use vectors in place
                //  of arrays because vectors more easily convert to Python tuples)
                self.getGreatCircleLocations(lat, lon, depth);

                // return a tuple of the values
                return py::make_tuple(lat, lon, depth);

            })

        .def("getNodeSeparation",
            [](SlbmInterface &self, const int node1, const int node2)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double distance;

                // call method, store results into the variables in memory
                self.getNodeSeparation(node1, node2, distance);

                // return a tuple of the values
                return distance;

            })

        .def("getNodeAzimuth",
            [](SlbmInterface &self, const int node1, const int node2)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double azimuth;

                // call method, store results into the variables in memory
                self.getNodeAzimuth(node1, node2, azimuth);

                // return a tuple of the values
                return azimuth;

            })

        .def("getTravelTimeUncertainty1D",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double travelTimeUncertainty;

                // call method, store results into the variables in memory
                self.getTravelTimeUncertainty1D(travelTimeUncertainty);

                // return a tuple of the values
                return travelTimeUncertainty;

            })

        .def("getSlownessUncertainty",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double slownessUncertainty;

                // call method, store results into the variables in memory
                self.getSlownessUncertainty(slownessUncertainty);

                // return a tuple of the values
                return slownessUncertainty;

            })

        .def("getZhaoParameters",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double Vm, Gm, H, C, Cm;
                int udSign;

                // call method, store results into the variables in memory
                self.getZhaoParameters(Vm, Gm, H, C, Cm, udSign);

                // return a tuple of the values
                return py::make_tuple(Vm, Gm, H, C, Cm, udSign);

            })

        .def("getPgLgComponents",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double tTotal, tTaup, tHeadwave, pTaup, pHeadwave, trTaup, trHeadwave;

                // call method, store results into the variables in memory
                self.getPgLgComponents(tTotal, tTaup, tHeadwave, pTaup, pHeadwave, trTaup, trHeadwave);

                // return a tuple of the values
                return py::make_tuple(tTotal, tTaup, tHeadwave, pTaup, pHeadwave, trTaup, trHeadwave);

            })

        .def("getDistAz",
            [](SlbmInterface &self, const double lat1, const double lon1, 
                                    const double lat2, const double lon2)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double distance, azimuth, naValue;
                naValue = numeric_limits<double>::quiet_NaN();  // use NaN for error values

                // call method, store results into the variables in memory
                self.getDistAz(lat1, lon1, lat2, lon2, distance, azimuth, naValue);

                // return a tuple of the values
                return py::make_tuple(distance, azimuth);

            })

        .def("movePoint",
            [](SlbmInterface &self, const double lat1, const double lon1, 
                                    const double distance, const double azimuth)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double lat2, lon2;

                // call method, store results into the variables in memory
                self.movePoint(lat1, lon1, distance, azimuth, lat2, lon2);

                // return a tuple of the values
                return py::make_tuple(lat2, lon2);

            })

        .def("getPiercePointSource",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double lat, lon, depth;

                // call method, store results into the variables in memory
                self.getPiercePointSource(lat, lon, depth);

                // return a tuple of the values
                return py::make_tuple(lat, lon, depth);

            })

        .def("getPiercePointReceiver",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double lat, lon, depth;

                // call method, store results into the variables in memory
                self.getPiercePointReceiver(lat, lon, depth);

                // return a tuple of the values
                return py::make_tuple(lat, lon, depth);

            })

        .def("getGreatCirclePoints",
            [](SlbmInterface &self, const double lat1, const double lon1,
                                    const double lat2, const double lon2,
                                    const int npoints)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<double> lats, lons;

                // call method, store results into the variables in memory
                self.getGreatCirclePoints(lat1, lon1, lat2, lon2, npoints, lats, lons);

                // return a tuple of the values
                return py::make_tuple(lats, lons);

            })

        .def("getGreatCirclePointsOnCenters",
            [](SlbmInterface &self, const double lat1, const double lon1,
                                    const double lat2, const double lon2,
                                    const int npoints)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<double> lats, lons;

                // call method, store results into the variables in memory
                self.getGreatCirclePoints(lat1, lon1, lat2, lon2, npoints, lats, lons);

                // return a tuple of the values
                return py::make_tuple(lats, lons);

            })


        // overloaded + return-by-reference methods
        //----------------------------------------------------------------------
        // if the method is both overloaded *and* return-by-reference, we can
        // just make a lambda function and let C++ figure out the overload

        .def("getWeights",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeId;
                vector<double> weight;

                // call method, store results into the variables in memory
                self.getWeights(nodeId, weight);

                // return a tuple of the values
                return py::make_tuple(nodeId, weight);
            })

        .def("getActiveNodeWeights",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeId;
                vector<double> weight;

                // call method, store results into the variables in memory
                self.getActiveNodeWeights(nodeId, weight);

                // return a tuple of the values
                return py::make_tuple(nodeId, weight);
            })

        .def("getGreatCircleNodeInfo",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<vector<int> > neighbors;
                vector<vector<double> > coefficients;

                // call method, store results into the variables in memory
                self.getGreatCircleNodeInfo(neighbors, coefficients);

                // return a tuple of the values
                return py::make_tuple(neighbors, coefficients);

            })

        .def("getNodeNeighbors",
            [](SlbmInterface &self, const int nodeId)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> neighbors;

                // call method, store results into the variables in memory
                self.getNodeNeighbors(nodeId, neighbors);

                // return a tuple of the values
                return neighbors;

            })

        .def("getActiveNodeNeighbors",
            [](SlbmInterface &self, const int nodeId)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> neighbors;

                // call method, store results into the variables in memory
                self.getActiveNodeNeighbors(nodeId, neighbors);

                // return a tuple of the values
                return neighbors;

            })

        .def("getNodeNeighborInfo",
            [](SlbmInterface &self, const int nodeId)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> neighbors;
                vector<double> distance, azimuth;

                // call method, store results into the variables in memory
                self.getNodeNeighborInfo(nodeId, neighbors, distance, azimuth);

                // return a tuple of the values
                return py::make_tuple(neighbors, distance, azimuth);

            })

        .def("getActiveNodeNeighborInfo",
            [](SlbmInterface &self, const int nodeId)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> neighbors;
                vector<double> distance, azimuth;

                // call method, store results into the variables in memory
                self.getActiveNodeNeighborInfo(nodeId, neighbors, distance, azimuth);

                // return a tuple of the values
                return py::make_tuple(neighbors, distance, azimuth);

            })

        .def("getTravelTimeUncertainty",
            [](SlbmInterface &self, const bool calcRandomError)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double travelTimeUncertainty;

                // call method, store results into the variables in memory
                self.getTravelTimeUncertainty(travelTimeUncertainty, calcRandomError);

                // return a tuple of the values
                return travelTimeUncertainty;

            })


        // complicated methods
        //----------------------------------------------------------------------
        // for various reasons, PyBind doesn't like to deal with these
        // functions automatically, so we'll call them with lambda functions

        .def("setGridData",
            [](SlbmInterface &self, const int nodeId, vector<double> depths,
                vector<double> pvelocity, vector<double> svelocity, vector<double> gradient)
            {
                // call method
                // (using vector.data() trick, here, so we can use vectors in place
                //  of arrays because PyBind likes vectors and not C-style arrays[])
                self.setGridData(nodeId, depths.data(), pvelocity.data(), svelocity.data(), gradient.data());
            })

        .def("setActiveNodeData",
            [](SlbmInterface &self, const int nodeId, vector<double> depths,
                vector<double> pvelocity, vector<double> svelocity, vector<double> gradient)
            {
                // call method
                // (using vector.data() trick, here, so we can use vectors in place
                //  of arrays because PyBind likes vectors and not C-style arrays[])
                self.setActiveNodeData(nodeId, depths.data(), pvelocity.data(), svelocity.data(), gradient.data());
            })

        .def("getInterpolatedPoint",
            [](SlbmInterface &self, const double lat, const double lon)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<int> nodeIds;
                vector<double> coefficients, depth, pvelocity, svelocity;
                double pgradient, sgradient;

                // call the method
                self.getInterpolatedPoint(lat, lon, nodeIds, coefficients, depth, pvelocity, svelocity, pgradient, sgradient);

                // return a tuple of the values
                return py::make_tuple(nodeIds, coefficients, depth, pvelocity, svelocity, pgradient, sgradient);

            })

        .def("getInterpolatedTransect",
            [](SlbmInterface &self, const vector<double> lat, vector<double> lon)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                vector<vector<int> > nodeId;
                vector<vector<double> > coefficients;
                vector<vector<double> > depth;
                vector<vector<double> > pvelocity;
                vector<vector<double> > svelocity;
                vector<double> pgradient;
                vector<double> sgradient;
                int nInvalid;

                // call the method
                self.getInterpolatedTransect(lat, lon, nodeId, coefficients, depth, pvelocity, svelocity, pgradient, sgradient, nInvalid);

                // return a tuple of the values
                return py::make_tuple(nodeId, coefficients, depth, pvelocity, svelocity, pgradient, sgradient, nInvalid);

            })

        .def("getCHMax",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double chMax;

                // call method, store results into the variables in memory
                self.getCHMax(chMax);

                // return a tuple of the values
                return chMax;

            })

        .def("getMaxDistance",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double maxDistance;

                // call method, store results into the variables in memory
                self.getMaxDistance(maxDistance);

                // return a tuple of the values
                return maxDistance;

            })

        .def("getMaxDepth",
            [](SlbmInterface &self)  // lambda function (minimum argument of "self")
            {
                // initialize variables in memory
                double maxDepth;

                // call method, store results into the variables in memory
                self.getMaxDepth(maxDepth);

                // return a tuple of the values
                return maxDepth;

            })

        .def("setCHMax",
            [](SlbmInterface &self, const double chMax)  // lambda function (minimum argument of "self")
            {
                // call method
                self.setCHMax(chMax);

            })

        .def("setAverageMantleVelocity",
            [](SlbmInterface &self, const int type, const double velocity)  // lambda function (minimum argument of "self")
            {
                // call method
                self.setAverageMantleVelocity(type, velocity);

            })

        .def("setMaxDistance",
            [](SlbmInterface &self, const double maxDistance)  // lambda function (minimum argument of "self")
            {
                // call method
                self.setMaxDistance(maxDistance);

            })

        .def("setMaxDepth",
            [](SlbmInterface &self, const double maxDepth)  // lambda function (minimum argument of "self")
            {
                // call method
                self.setMaxDepth(maxDepth);

            })

        ;

}
