Archive

Archive for the ‘Computer Science’ Category

Extendendo Python com C/C++ via PyBindGen e CTypes

sábado, 5 nov 2011; \44\UTC\UTC\k 44 6 comentários

Depois de um tempo sem postar no Ars Physica vim postar sobre algo totalmente diferente do que eu costumo escrever por aqui: programação. Como grande parte dos físicos hoje em dia, meu dia-a-dia consiste em grande parte em escrever programas de computador para resolver problemas e fazer cálculos. Todo começo de projeto de programação impõe um dilema para quem vai dedicar as próximas semanas (ou meses, anos…) escrevendo uma nova ferramenta: que linguagem de programação usar? Eu escolhi a combinação de C/C++ e Python.

As razões para essa escolha são muitas: Python é uma linguagem bastante simples, que permite prototipagem rápida e desenvolvimento de soluções com pouca dor de cabeça, e com uma ampla gama de módulos e bibliotecas prontas para os mais diversos fins (numpy, scipy, matplotlib, entre centenas de outras…). Entretanto código escrito puramente em Python é extremamente lento, por diversas razões. Isso faz com que não seja possível escrever uma simulação em python puro se pretende-se ter resultado em um tempo razoável. O ideal portanto é usar como cavalo de carga uma outra linguagem, que seja capaz de produzir binários eficientes, que rodem rápido no seu computador. Essa linguagem deve ser usada nas partes onde eficiência e tempo de execução são críticos, enquanto o Python pode ser usado para lidar com partes que geralmente são chatas de se fazer nessas linguagens de mais baixo nível: lidar com strings, arquivos, operações de sistema, geração de código, parsing,…

Como exemplo, no meu atual projeto no doutorado eu uso C/C++ para fazer uma simulação de Monte Carlo, e o Python para organizar as simulações, rodar a simulação para diversos valores de parâmetros diferentes, salvar os resultados em arquivos organizadinhos, enviar os processos para rodar nos diversos nós do cluster do departamento, etc.

Existem dezenas de formas de integrar Python com outras linguagens, de C/C++ e Fortran até Haskell e Emacs Lisp. Entretanto até hoje eu usava a mais boba: compilava um programa em C ou C++ que aceitava parâmetros de linha de comando, e de dentro do código do python abria um pipe para chamar o executável C com os parâmetros adequados com uma chamada de sistema. É uma gambiarra que funciona, mas não deixa de ser uma gambiarra. O ideal é compilar o seu código como uma biblioteca compartilhada que exporta objetos que o interpretador do Python consegue ler. A forma padrão de fazer isso é importar o cabeçalho ‘Python.h’ e usar o API contido lá para criar esses objetos. Isso não é exatamente difícil de fazer, mas é um trabalho sacal e bem repetitivo. É bom ter formas de automatizar esse trabalho e apenas se preocupar em escrever bem seu código em C, sem se preocupar se ele vai ser ou não carregado no python depois.

CTypes

Se o seu código é em C (e não C++) a maneira mais fácil de fazer isso é usando o CTypes – um módulo presente na biblioteca padrão do Python capaz de carregar bibliotecas compartilhadas feitas em C. Por exemplo, suponha que você deseja criar uma função que some dois inteiros e retorne o resultado. O código fonte está nos arquivos teste.h e teste.c:

//arquivo: teste.h
int add(int x, int y);

//arquivo: teste.c
#include "teste.h"
int add(int x, int y){
  return (x + y);
}

Note que esse é um código em C “vanilla”, sem nenhuma referência ao fato de que ele será depois usado no Python. Tudo o que é preciso para disponibilizar a função ‘add’ no python é compilar esse código como uma biblioteca compartilhada:

gcc -fPIC -o libteste.o -c teste.c
gcc -shared -o libteste.so libteste.o

Isso deve gerar um arquivo ‘libteste.so’, que é um binário que possui as instruções da função ‘int add(int, int)’ de forma que pode ser acessado por outros binários em C. Para chamar esse binário dentro do Python com o CTypes é muito fácil:

from ctypes import cdll

libteste = cdll.LoadLibrary("./libteste.so")
# eh necessario passar o caminho completo para o binario pois ele nao esta no PYTHONPATH
x = libteste.add(5, 2)
print x

Esse script deve retornar o valor ‘7’, conforme esperado. Difícil, né?

Quando sua função retorna um tipo que não seja ‘int’, é necessário ainda informar ao Python qual é o tipo adequado para converter os objetos do python antes de passá-los para a função em C. O CTypes oferece uma gama de tipos correspondentes a todos os tipos que podem ser criados em C padrão:

Tipo no CType Tipo no C Tipo no Python
c_bool _Bool bool (1)
c_char char 1-character string
c_uint unsigned int int/long
c_long long int/long
c_float float float
c_double double float
c_char_p char * (NUL terminated) string or None

Por exemplo, considere a seguinte função:

//arquivo: teste.h
double c_raizq(double x);

//arquivo: teste.c
#include <math.h>
#include "teste.h"

double c_raizq(double x){
  return sqrt(x);
}

Nesse caso, ao abrir a biblioteca (compilada exatamente como antes) será necessário dar mais informação a respeito dos tipos dessa função:

from ctypes import cdll
from ctypes import *

libtest = cdll.LoadLibrary("./libtest.so")
raiz = libtest.c_raizq
raiz.restype  = c_double
raiz.argtypes = [c_double]

x = raiz(2)

print x

Toda função importada do C tem as duas propriedades ‘restype’ – que é o tipo que a função deve retornar – e ‘argtypes’ – que é uma lista dos tipos que essa função recebe como parâmetros, na ordem em que eles aparecem no código em C.

Quando for necessário usar ponteiros, arrays, structs ou enums, a coisa pode ficar um pouquinho mais complicada, mas nada que faça o código crescer muito mais do que isso. Por exemplo, suponha que queremos exportar o seguinte código para o Python:

//arquivo: teste.h

struct cvec{
  double x;
  double y;
};

typedef struct cvec vector;
double norm(vector * point);  

//arquivo: teste.c

#include "teste.h"
#include <math.h>

double norm(vector * point){
  return sqrt(point->x * point->x + point->y * point->y);
}

Precisamos de uma estrutura similar ao struct ‘vector’ e de portar a função ‘norm’. Note que o argumento dessa função é um ponteiro para a struct vector. O código Python para fazer isso segue abaixo:

from ctypes import cdll
from ctypes import *

#imitando a struct vector 
class vector(Structure):
    _fields_ = [("x", c_double) ,
                ("y", c_double)]

libtest = cdll.LoadLibrary("./libtest.so")
norm = libtest.norm
norm.restype  = c_double
norm.argtypes = [POINTER(vector)]

vecc = vector(5,2)
print norm(pointer(vecc))

A classe vector imita a estrutura do struct vector, e as funções POINTER e pointer são usadas respectivamente para informar que o tipo do argumento é um ponteiro e obter um ponteiro para o objeto ‘vecc’. Structs e unions deve ser replicadas no Python por classes que herdam das superclasses Structure e Union, respectivamente.

Enfim, o CTypes fornece um API completo para usar qualquer código C padrão dentro do Python com um mínimo de boilerplate e nenhuma interferência no código original. Não é preciso reescrever suas funções nem entender a estrutura do API do Python. Apenas compilar seu código como uma biblioteca compartilhada.

PyBindGen

Infelizmente o CTypes não é capaz de ler binários de C++. A razão é simples: não existe um padrão para os binários de C++ e cada compilador implementa interfaces diferentes para seus binários. A esperança é que com o estabelecimento do padrão C++11 isso possa ser resolvido, mas isso é uma questão para o futuro. No entanto existe uma biblioteca feita em Python capaz de gerar bindings de códigos em C++ sem interferir no código e com o mínimo de esforço. Por exemplo, suponha que temos uma classe feita em C++ que representa pontos em 2 dimensões, com alguns métodos úteis:

//arquivo Vector.hpp
#include <cmath>
class Vector {
private:
  double x;
  double y;
public:
  Vector(double _x, double _y) : x(_x), y(_y) {}; //construtor

  double norm();               // retorna tamanho do vector
  void reflectO();           // reflete o vetor através da origem
  void rotate(double theta); // roda o vetor em torno da origem por um angulo theta
};

//arquivo Vector.cpp
#include "Vector.hpp"

double Vector::norm() {
  return x*x + y*y;
}

void Vector::reflectO(){
  x = -x;
  y = -y;x
}

void Vector::rotate(double theta){
  double xx = cos(theta) * x - sin(theta) * y;
  double yy = sin(theta) * x + cos(theta) * y;
  x = xx;
  y = yy;
}

Essa classe cria um vetor com duas componentes, com métodos que calculam a norma, refletem o vetor através da origem e rodam por um certo angulo. Para tornar essa classe disponível para o Python é preciso criar um script que gera automaticamente os bindings que devem ser então compilados em um módulo. A estrutura do script é bem simples – primeiro você deve criar um módulo e adicionar ao módulo a classe que deseja exportar, e em seguida adicionar os métodos à classe:

#arquivo: setupBinding.py
#! /usr/bin/env python

import sys
import pybindgen
from pybindgen import param, retval

#Modulo Vector
mod = pybindgen.Module("Vector")

#o modulo inclui o header Vector.hpp
mod.add_include('"Vector.hpp"')

#Adicionando a classe:
klass = mod.add_class('Vector')

#Adicionando o construtor:
klass.add_constructor([param('double', '_x'), param('double', '_y')])

#Adicionando os metodos:
klass.add_method('norm', retval('double'), [])
klass.add_method('reflectO', None, [])
klass.add_method('rotate'  , None, [param('double', 'theta')])

#imprime o binding na tela
mod.generate(sys.stdout)

Note a sintaxe dos comandos:

  • add_constructor([param(‘tipo’, ‘nome’),…]) – essa função recebe uma lista com os parametros que o construtor recebe. Se houver mais de um construtor, eles devem ser todos adicionados em sequencia.
  • add_method(‘nome’, retval(‘tipo de retorno’), [param(‘tipo_do_parametro1′, ‘nome1′), …]) – essa função recebe o nome do método, o tipo do valor que o método retorna e uma lista com os tipos dos parametros de entrada.

Ao rodar esse script com ‘python setupBinding.py’, ele imprime na tela um código em C que é um binding para o código contido em ‘Vector.cpp’ e ‘Vector.hpp’. Ao compilar esses bindings, teremos um módulo Vector que pode ser importado dentro do python como qualquer outro módulo:

import Vector

foo = Vector.Vector(1,2)
print foo.norm()
foo.rotate(0.2)

Compilar esse módulo é só um pouquinho mais complicado do que no caso do CTypes. Em primeiro lugar é preciso compilar uma biblioteca compartilhada como anteriormente:

g++ -fPIC -c -o libvector.o  Vector.cpp
g++ -shared  -o libvector.so libvector.o

sh Isso cria os arquivos ‘libvector.o’ e ‘libvector.so’. E então devemos gerar os bindings:

python setupBinding.py > bindVector.c

sh E compilar uma biblioteca compartilhada com os bindings:

g++ -fPIC -I/usr/include/python2.7 -c -o bindVector.o bindVector.c
g++ -shared -o Vector.so -L. -lvector bindVector.o

Note que é preciso passar para o compilador o caminho para os headers do python no seu sistema – no meu caso a versão 2.7 do python no linux está em ‘usr/include/python2.7‘. Também é preciso passar para o linker o caminho atual, onde está os arquivos ‘libvector.so’ e ‘libvector.o’ – que é a pasta atual onde a compilação está sendo feita. Isso é feito com as flag “-L. -lvector”. Isso cria o arquivo Vector.so, que contém o módulo Python que pode ser carregado através do comando “import”. Note que o nome do arquivo deve ser o mesmo nome do módulo conforme adicionado no script que gerou os bindings.

Antes de tentar importar o arquivo no python, é preciso adicionar o caminho onde o arquivo ‘Vector.so’ se encontra nas variáveis de ambiente PYTHONPATH e LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
export PYTHONPATH=$PYTHONPATH:.

Agora o módulo Vector pode ser usado normalmente:

import Vector

foo = Vector.Vector(1,2)
print foo.norm()
foo.rotate(0.2)

Enfim. Espero que isso ajude quem, como eu, vem quebrando a cabeça com isso a muito tempo e já testou diversas ferramentas (SWIG, SIP, Cython/Pyrex, Boost::Python, etc, etc, etc… ). As documentações oficiais das ferramentas usadas nesse post podem ser encontradas aqui:

Reconstrução 3D via fotos…

sábado, 27 nov 2010; \47\UTC\UTC\k 47 Deixe um comentário

Só pra animar um pouco esse sábado cinzento daqui, aqui vai uma notícia bem legal: 3-D mashup of Rome from Flickr pics.

Ou seja, fizeram uma reconstrução 3D — de monumentos em cidades como Roma e Berlim — a partir de fotos disponíveis publicamente (e.g., Flickr e Google Images). Tecnologia sensacional! :twisted:

[N.B.: Versão no Twitter.]

SciBloWriMo…

segunda-feira, 8 nov 2010; \45\UTC\UTC\k 45 1 comentário

O mês de Novembro é conhecido no meio literário como NaNoWriMo, National Novel Writing Month.

Um pessoal da Matemática decidiu pegar carona nessa idéia de criar o MaBlogWriMo: Math Blog Writing Month. A idéia, como descrita no link, é a de se escrever todo dia um post com até 1.000 palavras sobre matemática. :cool:

Então, parafraseando ambos esses eventos, vou começar o SciBloWriMo: Science Blog Writing Month! :twisted:

Eu vou aproveitar que vou dar uma palestra na conferência Miami 2010 e pegar uma carona pra falar dum tema que eu já venho trabalhando há algum tempo: o espaço de soluções (aka moduli space) de teorias quânticas de campo e suas simetrias. Esse será um dos temas do SciBloWriMo aqui no AP.

O outro tema é o de um trabalho que eu venho realizando atualmente, em colaboração com um pessoal da Neurociência, sobre o funcionamento hierárquico e maçissamente paralelo do cérebro, chamado Ersätz-Brain.

Assim que os posts forem ficando prontos, eu os linko aqui,

  • Álgebra, Teoria da Representação e Picard-Lefschetz;
  • Neurociência e o Projeto Ersätz-Brain: Teoria de Gauge, Variáveis de Nós e o Funcionamento Hierárquico do Cérebro.

É isso aí: espero que ninguém esteja com medo do frio! :wink:

Semantic Web: Web 3.0…

domingo, 16 mai 2010; \19\UTC\UTC\k 19 Deixe um comentário

Quem quiser ver um pouco mais sobre o assunto,

O mais incrível disso tudo é que a idéia original do TBL sempre foi essa: usar metadata pra “organizar” e “concatenar” a informação. Entretanto, infelizmente, apenas agora (quantas décadas depois da “invenção” da web? :razz: ) a idéia original está sendo apreciada como se deve.

Cálculo Exterior para Elementos Finitos…

sábado, 6 mar 2010; \09\UTC\UTC\k 09 Deixe um comentário

ResearchBlogging.org

O artigo Finite element exterior calculus: from Hodge theory to numerical stability trata dum tópico que eu gosto muito: análise numérica feita com ferramentas modernas (e.g., Cohomologia de de Rham e Teoria de Hodge).

Meu interesse sobre esse tipo de tópico começou cedo, quando na graduação eu comecei a lidar com problemas numéricos. A noção de que a estabilidade e convergência do método numérico deveria variar com a particular discretização (“mesh”) escolhida sempre ficou atrás da minha orelha. Mas, naquelas épocas, ainda estando na graduação, pouco era possível se fazer. Mas a vontade de aplicar essas idéias em Lattice Gauge Theory sempre me provocou. :wink:

Um pouco mais tarde, já na pós (mestrado), eu trombei com alguns artigos interessantes que, novamente, morderam essa mesma pulguinha,

Meu interesse por esses artigos era claro: o esquema de discretização deles tenta preservar as simetrias de Lie do problema original. Isso é particularmente importante se o objetivo é simular problemas que envolvem Quebra de Simetria! :idea: :cool:

Um pouco de tempo depois… me aparece o seguinte artigo, A Discrete Exterior Calculus and Electromagnetic Theory on a Lattice; que, mais tarde, seria seguido pelos seguintes artigos: Discrete Differential Geometry on Causal Graphs e Differential Geometry in Computational Electromagnetics.

A idéia, então, era novamente clara: aplicar esse mecanismo de Cálculo Exterior Discreto em Teorias de Gauge! :idea: :cool:

Afinal de contas, quem sabe, não daria pra juntar ambas as idéias: usar Cálculo Exterior Discreto de forma a preservar as simetrias [de Lie] do problema no contínuo :!: O que será que poderia sair daí?! (De fato, não tenho a menor noção, infelizmente nunca tive tempo de voltar e morder essa questão. Mas, taí um problema prum doutorado… :wink: )

Bom, depois de tudo isso, aparece o artigo que motivou esse post — eu tinha que falar alguma coisa a respeito dele.

Na verdade, esse artigo vai mais longe, extendendo o trabalho feito anteriormente, definindo apropriadamente uma Teoria de Hodge para Elementos Finitos, e avaliando as conseqüências para a consistência (“well-posedness of the Cauchy problem”; algo que varia muito com as particularidades da questão em mãos) e estabilidade numérica do problema. Portanto, as técnicas disponíveis agora são muito mais robustas! (O que só me deixa cada vez mais curioso pra saber a resposta das questões acima… :wink: )

É isso aí: a leitura é excelente, a diversão é garantida… e eu não me responsabilizo por noites de sono perdidas (por causa das questões acima)! :twisted:

Referências

  • Arnold, D., Falk, R., & Winther, R. (2010). Finite element exterior calculus: from Hodge theory to numerical stability Bulletin of the American Mathematical Society, 47 (2), 281-354 DOI: 10.1090/S0273-0979-10-01278-4

As melhores ferramentas de colaboração online…

quarta-feira, 17 fev 2010; \07\UTC\UTC\k 07 Deixe um comentário

Eu trombei no Mind Map abaixo, que faz uma lista (com recomendações quando necessário) das melhores ferramentas de colaboração online de 2009, e achei que vcs pudessem gostar e aproveitar também. :wink:

Best Online Collaboration Tools 2009; by Robin Good

Best Online Collaboration Tools 2009

(Versão PDF da imagem acima: Best Online Collaboration Tools 2009, by Robin Good (PDF, 220Kb).)

Quem tiver mais dicas, ou achar que faltou alguma coisa no MindMap acima… é só mandar pau nos comentários! :twisted:

Google Public DNS…

quinta-feira, 3 dez 2009; \49\UTC\UTC\k 49 Deixe um comentário

Hoje o Google pôs no ar um serviço de DNS,

A TechCrunch tem alguns comentários em Google Gets Into The DNS Business. Here’s What That Means; e o LifeHacker também tem alguns comentários, Google Public DNS Aims to Speed Up Your Browsing [DNS].

Vale a pena dar uma testada: os benchmarks que eu fiz por aqui foram bastante positivos. (E, claro, eu me empolguei um pouco… :wink: )

Fora isso, uma notícia um tanto inusitada, também vindo do Google,

Ou seja, os caras preferem evitar contratações “em massa”, pra garantir o bom equilíbrio do “ecossistema” chamado “mercado”. :cool:

Atualizado (2009-Dec-03 @ 14:26h): Agora o Slashdot está dando a notícia também, Google Launches Public DNS Resolver. (Sim, estou repassando esta notícia em tempo real! :twisted: )

E pra quem estiver interessado em dar uma “tunada” no próprio site, eis outra diquinha,

Diversão garantida… :cool:

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Junte-se a 70 outros seguidores

%d blogueiros gostam disto: