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()
BokehJS successfully loaded.
In [18]:
# Añadimos al PYTHONPATH donde se ha compilado el módulo
# Desde setup.py
sys.path.append('../WrapperClases/build/lib.linux-x86_64-3.4')
# Desde eclipse
#sys.path.append('../WrapperClases/wrapper_utiles/wrapper_clases')
In [19]:
import wrapper_utiles as wu
In [8]:
import wrapper_utiles as wu
def plot(self):
    indice=pd.date_range(self.primera_fecha(),self.ultima_fecha_valida(), freq='H')    
    data=dict(indice=indice,indice_text=[x.strftime("%Y-%m-%d %H") for x in indice],valores=self.valores())    
    source = ColumnDataSource(data)
    TOOLS="pan,xwheel_zoom,box_zoom,reset,hover"
    figura = Figure(plot_width=1000, plot_height=500, tools=TOOLS, x_axis_type='datetime', toolbar_location="left",title=self.descripcion)
    figura.line('indice', 'valores',source=source, color="Black",legend=self.descripcion)
    hover = figura.select(dict(type=HoverTool))
    hover.tooltips = [
                    ("(x,valor)", "(@indice_text, $y{1.11})"),
    ]
    show(figura)
    
wu.serie.Serie.plot=plot

Leemos los datos e instaciamos el objeto serie de la clase extendida utiles::Serie

In [9]:
parse = lambda x,y: dt.datetime.strptime(x+" "+str(int(y)-1), '%Y%m%d %H')
datos=pd.read_csv('datos_new_england.txt',parse_dates=[['fecha','hora']],date_parser=parse,index_col='fecha_hora',dtype={'observacion':np.float,'temperatura':np.float})
In [10]:
valores=datos.temperatura.values
fecha_inicial=datos.index[0]
resolucion=dt.timedelta(seconds=3600)
serie=wu.serie.Serie(valores,fecha_inicial,resolucion,"Serie Temperatura")
In [20]:
serie.plot()

std::ostream &operator<<(std::ostream &os, const Serie &serie);

In [12]:
serie
Out[12]:
Valores:       [ 8.69, 8.01, 6.89, 6.22, 5.66, ... 6.76, 6.05, 6.05, 5.34, 5.20, ]
Num.Valores:   744; Fecha Inicial: 2011-Dec-01 00:00:00; Resolucion:    01:00:00
Descripcion:   Serie Temperatura

using vector::begin; using vector::end;

In [13]:
print ("Ultimos valores: {}".format(serie[-5:]))
Ultimos valores: [ 6.760993  6.050135  6.050135  5.344739  5.200192]

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

In [14]:
serie_copiada=serie.copiar()
serie_copiada == serie
Out[14]:
True

Serie operator+(const Serie& serie, double valor);

In [15]:
print ("Serie Original:\n{}\nSerie Sumanda:\n{}".format(serie,serie+10))
Serie Original:
Valores:       [ 8.69, 8.01, 6.89, 6.22, 5.66, ... 6.76, 6.05, 6.05, 5.34, 5.20, ]
Num.Valores:   744; Fecha Inicial: 2011-Dec-01 00:00:00; Resolucion:    01:00:00
Descripcion:   Serie Temperatura

Serie Sumanda:
Valores:       [ 18.69, 18.01, 16.89, 16.22, 15.66, ... 16.76, 16.05, 16.05, 15.34, 15.20, ]
Num.Valores:   744; Fecha Inicial: 2011-Dec-01 00:00:00; Resolucion:    01:00:00
Descripcion:   Serie Temperatura

Caso2 : Envolver una función donde uno de sus argumentos es de la clase utiles::Serie

Cuando hemos envuelto la clase utiles::Serie de la librería utiles, implicitamente boost.python nos ha registrado la clase, por lo que tenemos otra clase más registrada para envolver otros métodos de la librería utiles que tenga como argumento la clase utiles::Serie

WrapperClases/utiles/include/matematicas/convolucionar.h

#include <vector>
#include "../Serie.h"
namespace utiles {
namespace matematicas {
        utiles::Serie ConvolucionarSerie(const utiles::Serie &datos,const std::vector<double> &filtro);
    }
}

/home/pedro/repositorios_git/MeetUpMadrid/WrapperClases/wrapper_utiles/wrapper_utiles/src/matematicas/convolucionar.cpp

#include "../../include/matematicas/convolucionar.h"
#include <boost/python.hpp>
#include <include/matematicas/convolucionar.h>

namespace py = boost::python;

void wrapper_utiles::matematicas::exportar_convolucionar(){
    // Nos provee el espacio de nombre
    py::object modulo_convolucionar(py::handle<>(py::borrowed(PyImport_AddModule("wrapper_utiles.matematicas"))));
    py::scope().attr("matematicas") = modulo_convolucionar;
    py::scope util_scope = modulo_convolucionar;

    py::def("convolucionar", &utiles::matematicas::ConvolucionarSerie,
                "Convolucionar la serie de datos, primer elemento con el filtro, segundo elemento");
}

Demostración:

Con los mismos datos anteriores, aplicaremos un filtro paso alto a la serie

In [16]:
filtro_paso_alto = np.array([ 0.12940952,  0.22414387, -0.8365163 ,  0.48296291])
serie_conv       = wu.matematicas.convolucionar(serie,filtro_paso_alto)
serie_conv.plot()

Referencias

¿Preguntas?