In [1]:
from IPython.core.display import HTML
HTML("<style>.container { width:100% !important; }</style>")
Out[1]:

Introducción

  • Ventajas
  • Material
  • Estructura de la presentación

Ventajas

  • No repetir funcionalidades entre distintos entornos de programación.
  • Relación eficiencia / productividad

Material

Estructura De La presentación

  • Lo primero, quitarnos la vergenza. Nada mejor que con el claásico HolaMundo
  • Nos metemos de lleno en la extensión de funciones, con un ejemplo de menos a más.
  • Por último, la envoltura de una clase externa
  • Referencias
  • Preguntas

Hola Mundo

Caso: Función sencilla sin argumentos y ningún valor de retorno

WrapperFunciones/src/hola_mundo/ejemplo1.cpp

#include <iostream>
#include <string>
#include <boost/python.hpp>

void HolaMundo() {
    std::cout << "Hola Mundo" << std::endl;
}

BOOST_PYTHON_MODULE(ejemplo1)
{
    boost::python::def("hola_mundo", HolaMundo,"Funcion que muestra nuestro primer saludo");
}

Código para compilar:

g++ -I/usr/include/python3.4m -c -std=c++14 -fPIC  -o ejemplo1.o ejemplo1.cpp
g++ -shared -lboost_python3 -o "ejemplo1.so"  ./ejemplo1.o

std::vector <-> numpy

Caso: Tenemos una función que multiplica un factor a cada elemento del vector:

WrapperFunciones/src/vector/multiplicacion.cpp

#include <vector>
#include <algorithm>
#include "multiplicacion.h"

std::vector<double> Multiplicacion(const std::vector<double> &datos, double factor) {

    std::vector<double> datos_transformados(datos);

    std::transform(datos_transformados.begin(), datos_transformados.end(),
            datos_transformados.begin(),
            [&factor](auto a) {return (a*factor);});

    return datos_transformados;
}

1 Solución: Aproximación por medio de una función auxiliar que convierta los tipos

  • La función auxiliar wrapper_multiplicacion envuelve la función que queremos exponer

WrapperFunciones/src/vector/ejemplo7.cpp

#include <iostream>
#include <boost/python.hpp>
#include "boost/python/stl_iterator.hpp"
#include "boost/python/numeric.hpp"
#include <boost/assign/std/vector.hpp>
#include "multiplicacion.h"

namespace py = boost::python;

py::numeric::array wrapper_multiplicacion(const py::numeric::array& datos, double factor = 1) {
    // numpy -> std::vector
    py::stl_input_iterator<double> begin(datos), end;
    std::vector<double> datos_transformar(begin, end);

    // LLamada a la función
    auto nuevos_datos = Multiplicacion(datos_transformar, factor);

    // std::vector -> numpy
    py::list resultado;
    for (auto const &i : nuevos_datos)
        resultado.append(i);

    return py::numeric::array(resultado);
}

BOOST_PYTHON_FUNCTION_OVERLOADS(args_por_defecto_multiplicacion, wrapper_multiplicacion, 1, 2);
BOOST_PYTHON_MODULE(ejemplo7)
{
    py::numeric::array::set_module_and_type("numpy", "ndarray");
    py::def("wrapper_multiplicacion", wrapper_multiplicacion,args_por_defecto_multiplicacion());
}
In [2]:
import numpy as np

factor_multiplicacion=1.5
numero_de_elementos=5
vector = np.arange(numero_de_elementos,dtype=np.float64)
vector
Out[2]:
array([ 0.,  1.,  2.,  3.,  4.])
In [3]:
import ejemplo7
ejemplo7.wrapper_multiplicacion(vector,factor_multiplicacion)
Out[3]:
array([ 0. ,  1.5,  3. ,  4.5,  6. ])

2 Solución: Aproximación por medio del registro de tipos de conversión

  • Por medio de la creación de conversores explícitos, tanto de C++ a Python como viceversa
  • Ventajas: Una vez definido los conversores, la extensión del código c++ a python es directo. En mi repo están definidos los siguientes conversores:
C++ Python Notas
std::vector< double > numpy En realidad cualquier clase iterable que se pueda construir por medio de iteradores y que la clase que contenga, tenga definido el constructor copia
boost::posix_time::time_duration datetime.timedelta
boost::gregorian::date datetime.date
boost::posix_time::ptime datetime
boost::local_time::local_date_time datetime Por ahora, únicamente reconoce la zona horaria UTC

Ejemplos: std::vector a numpy y viceversa

WrapperFunciones/src/vector/ejemplo8.cpp

#include <iostream>
#include <boost/python.hpp>
#include "multiplicacion.h"
#include "../../../WrapperClases/convertidores/include/envoltorio_objetos_iterables.h" // Donde está definido el conversor

namespace py = boost::python;

BOOST_PYTHON_FUNCTION_OVERLOADS(args_por_defecto_multiplicacion, multiplicacion, 1, 2);
BOOST_PYTHON_MODULE(ejemplo8){
    convertidores::RegistrarObjetosIterables<std::vector<double>>(); // Se registra
    py::def("wrapper_multiplicacion", Multiplicacion);
}
In [4]:
import ejemplo8
ejemplo8.wrapper_multiplicacion(vector,factor_multiplicacion)
Out[4]:
array([ 0. ,  1.5,  3. ,  4.5,  6. ])

Wrappers De Clases / Librería Externa

Caso1 : Vamos a extender la clase Serie de una librería externa

WrapperClases/utiles/include/Serie.h

class Serie: private std::vector<double> {
    typedef std::vector<double> vector;
public:
    Serie();
    Serie(const std::vector<double> &serie, const boost::posix_time::ptime &fecha_inicio, 
          const boost::posix_time::time_duration &resolucion = boost::posix_time::seconds(3600),
          std::string descripcion = "");

    boost::posix_time::ptime         UltimaFechaValida() const;
    boost::posix_time::ptime         PrimeraFecha() const;
    Serie                            Copiar() const;    
    const std::vector<double>&       GetValores() const;    
    std::string                      GetDescripcion() const;    
    void                             SetDescripcion(std::string descripcion);    
    boost::posix_time::time_duration GetResolucion() const;

    bool operator==(Serie const& otra_serie) const;

    using vector::operator[];
    using vector::begin;
    using vector::end;
    using vector::size;
    using vector::iterator;

private:
    boost::posix_time::ptime fecha_inicio;
    boost::posix_time::time_duration resolucion;
    std::string descripcion;

};

Serie operator+(const Serie& serie, double valor);
Serie operator-(const Serie& serie, double valor);
std::ostream &operator<<(std::ostream &os, const Serie &serie);

WrapperClases/wrapper_utiles/wrapper_utiles/src/serie.cpp

py::class_<utiles::Serie>("Serie",
            py::init<std::vector<double>,boost::posix_time::ptime const&,py::optional<boost::posix_time::time_duration,std::string>>
                     (( py::arg("datos"),
                        py::arg("fecha_inicio"),
                        py::arg("resolucion")="3600",
                        py::arg("descripcion")=""
                      ), "Constructor de la serie") )
            .def("ultima_fecha_valida",&utiles::Serie::UltimaFechaValida) // boost::posix_time::ptime UltimaFechaValida() const;
            .def("primera_fecha",&utiles::Serie::PrimeraFecha) // boost::posix_time::ptime PrimeraFecha() const;
            .def_readonly("resolucion",&utiles::Serie::GetResolucion) // boost::posix_time::time_duration GetResolucion() const;
            .add_property("descripcion",&utiles::Serie::GetDescripcion,&utiles::Serie::SetDescripcion) // std::string GetDescripcion() const, void SetDescripcion(std::string descripcion);
            .def("valores", &utiles::Serie::GetValores,py::return_value_policy<py::copy_const_reference>()) //const std::vector<double> &GetValores() const;
            .def(str(py::self))  // std::ostream &operator<<(std::ostream &os, const Serie &serie);
            .def(repr(py::self)) // std::ostream &operator<<(std::ostream &os, const Serie &serie);
            .def(py::self + double()) // Serie operator+(const Serie& serie, double valor);
            .def(py::self - double()) // Serie operator-(const Serie& serie, double valor);
            .def("__eq__",&utiles::Serie::operator==) // bool operator==(Serie const& otra_serie) const;
            .def("__iter__",py::iterator<utiles::Serie>()) // using vector::iterator;
            .def("__getitem__",&GetItemSerie)       // using vector::operator[];
            .def("__getitem__",&GetItemSliceSerie); // using vector::begin; using vector::end;
double GetItemSerie(const utiles::Serie& self, int index) {
    if (index < 0)
        index += static_cast<int>(self.size());

    if (index < 0 || static_cast<int>(self.size()) <= index)
        throw std::out_of_range("Fuera de rango");

    return self[index];
}

py::numeric::array GetItemSliceSerie(const utiles::Serie& self,
        py::slice slice) {
    py::list resultado;
    py::slice::range<std::vector<double>::const_iterator> rango;
    try {
        rango = slice.get_indices(self.begin(), self.end());
    } catch (std::invalid_argument&) {
        return py::numeric::array(resultado);
    }

    for (; rango.start != rango.stop; std::advance(rango.start, rango.step)) {
        resultado.append(*rango.start);
    }
    resultado.append(*rango.start); //Ultimo elemento
    return py::numeric::array(resultado);
}

Demostración de los métodos envueltos

Leeremos y jugaremos con los datos de observación de demanda y temperatura de de un estado de new york

Cargamos los módulos

In [5]:
import datetime as dt
import pandas as pd
import numpy as np
import sys
from bokeh.plotting import Figure,output_notebook,show
from bokeh.models import ColumnDataSource,HoverTool
output_notebook()