En una década

He estado usando casi diario los Google+ Hangouts para videoconferencia (¿cómo se traduce eso?, ¿”pasadores de tiempo”?). Las razones son varias, pero la más importante, para mí, es que me evitan la molestia de instalar Skype, que cada vez me resultaba más desesperante.

Como he comentado no pocas veces en mi blog, detesto KDE, e incluido en eso va Qt. Compilarlo además en Gentoo es lentísimo, y prefiero usar mi procesador para cosas más interesantes. El cliente de Skype para Linux utiliza Qt, y al menos en Gentoo tienen la decencia de incluir una versión binaria de Qt junto con el paquete; pero de todas formas significa usar Qt, y si puedo siempre lo evito.

Además de Qt, Skype nunca mereció mucha confianza de mi parte; menos aún cuando fue comprado por Microsoft (que además casi garantiza que un buen cliente para Linux nunca existirá). Los Hangouts de Google+ me evitan todos estos problemas, y además Google me cae mejor que casi cualquier otra compañía, así que les doy el beneficio de la duda.

Técnicamente además los famosos hangouts están bastante padres; puede uno tener videoconferencia con N personas a la vez; corre todo dentro del navegador por lo que no es otra aplicación que hay que iniciar y configurar; y corre básicamente de forma perfecta en Linux, con un plugin para Chromium (que no dudo funcione también en Firefox) que mide en total como 21 Megabytes.

Comencé usando los hangouts en mi laptop, y la experiencia ahí ha sido básicamente perfecta excepto por dos problemas (ninguno relacionado con los hangouts): el primero es que mi laptop tiene unas bocinas que yo creo que son una broma, literalmente; y el segundo es que, por esas cosas que suelen ocurrir con Linux, el chipset que utiliza el bluetooth de mi laptop ahorita no está funcionando para que jalen mis audífonos bluetooth. Y hago énfasis en ahorita: hace unos meses funcionaba, y con casi toda seguridad en unos meses volverá a funcionar, sólo que como bluez pasó de la versión 4.101 a la 5, y no son compatibles, el proceso para que las aplicaciones que usan bluez se porten a la nueva versión puede tardar mucho. De hecho, es posible que con bluez 5 mis audífonos ya sirvan, sólo no he actualizado porque todo el resto del software sigue dependiendo de bluez 4.101.

Y para hacerlo más agraviante, al parecer todo lo relacionado con bluetooth funciona con bluez 4.101; incluso mis audífonos son reconocidos y ligados a la laptop. Sólo luego no aparecen como tarjeta de sonido externa. Como sea, no es grave; me pongo mis infalibles audífonos Genius, que además cuentan con micrófono incluido, y todo está chido.

De cualquier forma me dieron ganas de ver cómo funcionarían los hangouts en mi máquina de escritorio, en gran medida porque mi silla ahí es más cómoda, y el monitor es mucho más grande. Así que lo primero fue ver si servían bien los audífonos ahí; perdí unos quince minutos buscando dónde estaba el control de volumen para el micrófono, hasta que caí en cuenta de que PulseAudio es lo suficientemente inteligente como para no mostrarlo a menos que esté conectado.

Ni siquiera sabía que las computadoras ahora podían detectar cuándo estaba conectado un micrófono.

Luego fue la cámara de video, que es una antiquísima Creative Webcam NX que se conecta por USB a la computadora. Para que tengan una idea, la compré cuando pensé que me iría a hacer el posgrado a Canadá, así que sí tiene casi diez años conmigo; hablé de ella hace mucho. Bastante ha cambiado desde esa entrada; en particular, el controlador de la camarita está en el kernel desde hace años, y por lo que tengo entendido funciona perfecto… mientras en Windows a 64 bits de hecho nunca funcionó.

De nuevo tardé como veinte minutos tratando de encontrar mi camarita (el software; la camarita lleva años viviendo físicamente encima de mi monitor), hasta que por fin caí en cuenta de que por alguna razón el controlador no estaba compilado en mi kernel. En alguna actualización la opción de tener camaritas de video USB requirió alguna otra opción que no activé, y desde entonces no se había compilado el módulo. Así que recompilé el kernel, reinicié, y por fin tuve camarita de nuevo.

Y fue casi dolorosamente decepcionante:

Camarita

Camarita

Por supuesto las condiciones de luz en mi departamento apestan (más aún a las ocho de la noche), pero la calidad de la camarita es abismal. Tiene una resolución máxima de 352×288; en comparación, mi latop (que no es último modelo) tiene una resolución de 1280×1024. En mi monitor FullHD (1920×1080), la ventana de Cheese (el programa para tomar fotos de GNOME) tiene que escalar hacia arriba la imagen para que quepan los botones de su barra de herramientas. Y con tantito que se escale, a esa resolución, todo se ve súper pixeleado.

Así que descarté mi escritorio para usar los hangouts, a menos que mis interlocutores quieran ver sombras nada más. Lo que me impresionó, y que es el punto de esta entrada, es que hace diez años una camarita USB con resolución 352×288 no sólo no era rara, sino que tendía a estar en el grado alto del espectro. E independientemente de la resolución, la calidad del video (y fotos) que toman las camaritas actuales es muchísimo mejor que las de hace diez años.

En una década ocurrió que el hablar con alguien a cientos de kilómetros de distancia, y además verlos al mismo tiempo en tiempo real, se puede hacer de forma ridículamente sencilla, y con una calidad del video tal que uno puede ver los insectos caminando en las paredes detrás de los interlocutores (true story). A veces se me olvida que ya vivimos en el futuro.

Y por cierto, los hangouts funcionan sorprendentemente bien en teléfonos celulares inteligentes, si bien sólo los probé una vez, y ambos participantes usando red inalámbrica. Supongo que habrá que ver si funciona bien sobre 3G, aunque la verdad lo dudo. Mi teléfono no es 4G LT, pero me imagino que el próximo sí lo será.

Y entonces sí voy a sentir de verdad que vivimos en el universo de Star Trek.

Imprimir entrada Imprimir entrada

Mis fotos

Gracias a mi aplicación para subir fotografías, y a mi aplicación para editar etiquetas, terminé ya de organizar, etiquetar, respaldar y subir todas mis fotos, incluyendo las que tomé el fin de semana pasado cuando Juan se casó.

Galería

Galería

En el procesó afiné y mejoré varias cosas de mis aplicaciones, y me parece que ya son suficientemente robustas. La metodología que tengo para manejar mis fotos está ya bastante estandarizada, y se integra de manera perfecta con GNOME 3, así que espero no volver a quedarme tan atrasado en el mantenimiento de mi galería. Además, como ahora tengo disco duro de sobra, decidí liberar a mi laptop de un montón de cosas (videos e imágenes de CDs y DVDs, principalmente) que no tenían razón de estar ahí, con lo que puedo ya tener una copia de mi colección de fotos en mi computadora portátil.

No lo he platicado en el blog, pero estoy estrenando laptop (desde hace varios meses), y tiene un disco duro de estado sólido; esto es espectacular porque todo corre estúpidamente rápido… la desventaja es que es diminuto para el tamaño de discos duros al que estoy acostumbrado (tiene 128 GB). Es la primera laptop donde borré completamente el Windows que venía instalado; necesitaba el espacio. Actualmente mi colección de fotos ronda en los 21 Gigabytes; dado que son del orden de 9,000 fotografías, espero no llegar a los 60 Gigabytes pronto: me quedan menos de 40 Gigabytes libres en la laptop. Cuando llegue a ese tamaño, espero ya haber cambiado de computadora portátil.

Tener las fotos en la laptop (y no sólo aventadas en un directorio, sino además ya bien integradas en Shotwell) me permitirá actualizar mi galería incluso si salgo de México en un viaje largo; podré organizar las fotos en mi laptop, e incluso subirlas a la galería en línea desde donde quiera que esté. Gran parte del problema durante mis viajes largos de los últimos años fue que la base de datos de mis fotos en F-Spot estaba en México en mi máquina de escritorio, y no sabía cómo sincronizar dos instalaciones distintas del programa en máquinas diferentes. Con Shotwell ya sé cómo hacerlo: es sólo es cosa de mantener mi directorio $HOME/Pictures sincronizado entre las dos máquinas, y copiar la última versión del archivo $HOME/.local/share/shotwell/data/photo.db sobre la versión viejita en la máquina que se esté sincronizando.

Además de la copia en mi máquina de escritorio y en mi laptop, tengo una copia de mis fotos en mi media center (que tendré que escribir un programa que me permita exportar la base de datos de Shotwell y meterla en la base de datos de XBMC, porque si no sólo se puede ver fotos por directorio), otra en mi servidorcito Atom, y una más en una máquina debajo de siete capas de adamantium y kriptonita que la tengo corriendo en la Zona Fantasma. Y una copia más (pero con las fotos escaladas a 1024×768) en Xochitl en mi galería en línea, que los invito una vez más a que la exploren.

Imprimir entrada Imprimir entrada

Quick Photo Editor

Limpié y subí también mi aplicacioncita para editar etiquetas en fotos; la pueden encontrar en Github.

Quick Photo Editor

Quick Photo Editor

Llevo años escribiendo pequeñas aplicaciones que me quedo nada más para mí. No creo necesariamente que le vayan a servir a absolutamente nadie más, pero el hacerlas públicas me obliga a tener el código en buen estado, legible, y a escribir el mínimo de documentación e infraestructura necesarias para que no sea nada más un archivo en Vala, Python, C o Perl aventado en algún directorio de mi $HOME, que años después no tengo ni idea de qué hacía o por qué lo había escrito.

Esta aplicación está escrita en Vala, que me parece ahí reside en gran medida el futuro de GNOME; es muy divertido de programar, y los programas son razonablemente rápidos y con poco uso de memoria (contrario a C#). Además, el código es muy legible y compacto; no al grado de Python, pero me parece que sí más que C#. El programita, aunque su funcionalidad a lo mejor le es inútil a nadie que no sea yo, sirve también para estudiar un ejemplo pequeño, pero funcional, de cómo escribir una aplicación con autotools, usando gettext para internacionalización, cómo instalarle iconos, y otras cosas de ese estilo.

Imprimir entrada Imprimir entrada

Gallery 3 Uploader

Total que limpié mi programita para subir fotos a Gallery3; es básicamente para mi uso privado, pero consideré que a lo mejor a alguien le podría resultar útil al menos el módulo que se comunica con el API REST de Gallery3.

Como lo iba a hacer público, lo limpié, le puse una interfaz gráfica, lo hice que hablara varios idiomas (inglés y español, porque no sé otros), y le puse toda la parafernalia para que se pueda compilar haciendo la santísima trinidad de ./configure && make && make install. El resultado no sólo es más agradable a la vista; ahora puedo seleccionar fotos en Nautilus, hacerles click derecho, y abrirlas con esta aplicacionciota, lo cual las manda a mi galería en línea.

Gallery 3 Uploader

Gallery 3 Uploader

Le faltan muchas cosas: por ejemplo, tronará sin decir nada si alguna de las etiquetas que espero encontrar en las fotos falta, pero ya es útil para mí, y espero lo sea también para alguien más. El programita está en Github: https://github.com/canek-pelaez/g3uploader.

Ahora sólo tengo que limpiar la aplicación que edita las etiquetas.

Imprimir entrada Imprimir entrada

La vida a través de una cámara digital

En 2004, la Universidad de Waterloo me aceptó para que fuera a hacer mi maestría. Entre otras cosas por eso empecé este blog, porque quería escribir acerca de mis estudios en el extranjero. Por las mismas razones, estuve considerando desde febrero de 2005 el comprar una cámara digital, y en marzo Sergio, el hermano de Enrique, me hizo el favor de comprarme una Sony Cybershot DSC-P200 en el gabacho, que en abril por fin tuve en mis manos.

Por supuesto ya saben qué pasó: Conacyt decidió que la computación no era un “área estratégica” para México y no me becaron, así que me quedé aquí y pasaron los siguientes ocho años de mi vida. De eso no es esta entrada.

La entrada es de que una vez tuve mi cámara, de inmediato decidí que necesitaba un sitio en línea para poner mis fotos a la vista de todo mundo. Agarré e instalé Gallery, que era (y es, me parece) el programa más utilizado para mantener una galería en línea, y de inmediato me desagradó. Era lento, usaba mucho (y mal) JavaScript (o a lo mejor era sólo que entonces los navegadores traían pésimos motores de JS), y además no me gustó cómo se veía. Era la versión 1 del programa.

Instalé entonces Coppermine, que fue básicamente el primer programa alternativo a Gallery que encontré, y lo usé unos cuantos meses. En mi casa usaba F-Spot, que está escrito en C#, y que en ese momento me parecía un extraordinario programa. Claro, tenía entonces del orden de doce fotografías, entonces F-Spot hasta rápido parecía.

Mi colección de fotografías digitales estuvo durante varios años manejada, y de alguna manera controlada, por F-Spot. El programa no es para nada malo, sólo sufre el mismo problema que todos los programas escritos en C#: la memoria que consumen es ridículamente alta, y se vuelven lentísimos con no mucha información. Como sea, eso no lo descubriría sino hasta años después.

En 2005 todavía no lo descubría, porque les digo que tenía como quince fotos, pero mi uso de F-Spot causó que tuviera que dejar de usar Coppermine. F-Spot nunca fue pensado para usar álbumes; previendo que eso sería el futuro, F-Spot favorecía mejor las etiquetas, y entonces una foto puede pertenecer a más de una etiqueta. Uno puede emular la funcionalidad de álbumes con etiquetas, pero no al revés. Lo grave con Coppermine no fue que no tuviera etiquetas; era que no podía ni siquiera mover fotos entre álbumes. Además no podía subirlas fácilmente desde F-Spot (y en ese entonces la red era mucho más lenta), y todo se combinó para que decidiera dejar Coppermine.

Entonces volví a revisar Gallery, y seguía básicamente igual de malo que antes; pero por suerte ya estaba disponible el primer beta de Gallery2, así que decidí probarlo. Me gustó mucho más, pero lo que me convenció de usarlo fue que F-Spot tenía un plugin para mandar un conjunto de fotos a un álbum de Gallery2. Eso hizo mi vida mucho más sencilla.

Yo soy muy neurótico con mis colecciones, de lo que sea. De música, de películas, de videojuegos, o de fotos, me interesa que todo esté meticulosamente etiquetado y catalogado. En F-Spot podía ponerles a las fotos un comentario (donde generalmente pongo el nombre de los que aparecen en la foto, o el lugar donde estoy en el peor de los casos), y el plugin que subía las fotos a Gallery2 se encargaba de hacer que dicho comentario apareciera también en Gallery2. Era la gloria.

Así estuve durante años, muy feliz, aunque con varias incongruencias en mi galería en línea. Como F-Spot tenía varias etiquetas “principales” (Favoritos, Escondidas, Eventos, Lugares y Gente), yo traté de emular eso en Gallery2… lo cual es una enorme pendejada, porque terminé aventando casi todo al álbum de eventos. Además, por alguna razón que no comprendo, creé un álbum llamado “Pruebas” que aparecía ahí en la página principal de la galería, y que lo hizo durante varios años.

Como sea, quitando esas cosas todo medio funcionaba, y lo que más me importaba era que la información que metía con F-Spot a mis fotos, se conservaba cuando las subía a Gallery2. Hasta que un día los desarrolladores de F-Spot decidieron cambiar las cosas: lo que yo metía en F-Spot como comentarios a las fotos, se subía como el título a Gallery, pero entonces decidieron cambiarlo a que fuera el comentario. Y entonces no se veía esa información a nivel del álbum en Gallery2, se veía sólo en la página de la foto. Con eso podría haber vivido; lo que era horrible era que el título ahora era el nombre del archivo, algo del estilo dsc00768.jpg.

Pude parchar a F-Spot para que funcionara como lo hacía antes (de algo tiene que servir que sepa programar), pero ya para entonces, en 2008, se había comenzado a volver muy lento con todas mis fotos. Ya tenía del orden de 2,000 fotografías, y al programa le empezaba a pesar muchísimo. Además comenzó a estar súper inestable, y tenía que estar deshabilitando cosas para que no tronara.

A pesar de todo eso lo seguí usando, y aunque no de forma perfecta seguía funcionando lo más importante para mí, que podía pasar las fotos de F-Spot a Gallery2 preservando la información de las mismas.

Y entonces me fui a Europa durante tres meses, de enero a marzo de 2009, y se me ocurrió regresar con 2,500 fotografías más.

Cuando, después de meses, había metido todas las fotos de mi viaje a F-Spot, el programa ya era básicamente inusable para mí. Y además de todo, la subida de las mismas a Gallery2 no era raro que fallara de formas extrañas, lo cual dejaba una imagen de error en lugar de la foto (aunque sí generaba correctamente la miniatura, porque F-Spot era el que la generaba antes de subir la foto, lo que hacía todavía más difícil descubrir cuándo había fallado la transferencia).

Pero lo que hizo que me deshiciera de F-Spot fue que cuando regresé de California en 2009, donde visité a mi cuate Eddie en San Francisco, había estado guardando la información GPS de donde había estado, y decidí tratar de sincronizarla con las fotos (para que cada foto marcara dónde la había tomado). Y entonces me di cuenta de que las horas de las fotos estaba desfasadas por 6 horas, porque los idiotas de F-Spot estaban suponiendo que mi cámara estaba en horario GMT (UTC+0), y que como México (y mi escritorio) estaban configurados en America/Mexico_City (UTC-6), eso quería decir que tenía que moverle al tiempo de casi todas mis fotos.

No tienen idea de cómo me encabroné, porque nunca me preguntó o dijo nada al respecto, y yo pensaba que ya no podía rescatar el tiempo original. Así que cerré por última vez en mi vida F-Spot, y seguí trabajando en mi doctorado, viajando, y tomando fotos que aventaba al primer directorio que podía, sin ni siquiera pensar en que sería bueno algún día subirlas a mi galería.

Hasta que mi disco duro tronó, como comenté hace unos días.

Ya que recuperé mis fotos, y las respaldé en cuanta máquina pude respaldarlas, comencé a pensar en cómo restituir mi colección de fotos en mi máquina (sin usar F-Spot, claramente, que además al parecer dejaron ya de mantener: la última versión salió en 2010), y cómo después tener un sistema independiente de cualquier programa (o al menos de cualquier programa no escrito por mí) para sincronizarlo con mi galería en línea, que además migré a Gallery3 cuando me quedé sin novia, sin casa, sin dinero y sin trabajo.

Primero descubrí que las fechas modificadas por F-Spot eran las dadas por la etiqueta EXIF DateTimeOriginal, pero por suerte la fecha original estaba guardada en CreateDate, así que sólo escribí un script que comparara las dos fechas y reemplazara la primera con la segunda si acaso diferían; para eso utilicé exiftool. Luego decidí mover la información de F-Spot a las fotos directamente. Los comentarios que con tanto cuidado había metido a las mismas durante mis años de usar el programa estaban en una base de datos SQLite, así que escribí un programita en Python que sacaba esa información, y la guardaba en las etiquetas EXIF UserComment, Title, ImageDescription y Caption, porque me pareció que era mejor ser redundantemente redundante. Usé exiftool de nuevo para manipular las etiquetas de las fotos.

Ya que hice eso, decidí probar Shotwell, el nuevo programa para manejar fotos de GNOME 3. El programa está escrito en Vala, que todo el lenguaje me parece un maravilloso hack, y me gustó mucho cómo funciona. Sólo que entonces vi que en algunas fotos aparecían mis comentarios como títulos, y en otras sólo el nombre del archivo. Investigando (tuve que bajar hasta el código fuente de Shotwell), vi que lo que pasaba es que Shotwell usaba la etiqueta Iptc.Application2.Caption para el título, porque al parecer es lo más estándar. Esa etiqueta no es EXIF, es IPTC, así que tuve que usar el programa exiv2 para reacomodar los comentarios en mis fotos. Por suerte, todo esto ya era sólo escribir otro script que hiciera toda la chamba. También vi que Shotwell usa la etiqueta Xmp.dc.subject para etiquetas internas del programa, así que decidí que con eso haría mis álbumes.

Shotwell maneja álbumes a la antigüita, todos basados en fechas. Se pueden mezclar fotos entre álbumes, pero decidí mandar eso completamente al carajo: a partir de ahora, mis álbumes están definidos por un rango continuo de tiempo, y a la chingada con todo lo demás. Además de álbumes, Shotwell maneja etiquetas, pero son ortogonales los primeros a las segundas. De cualquier forma, decidí arbitrariamente que la única etiqueta que tendrían mis fotos sería el nombre del álbum al que pertenecen.

Como los álbumes de Shotwell están basados en tiempo, automaticamente divide todo en años, estos en meses, y ya dentro de los meses hay álbumes que pueden ser de un instante en el tiempo (si tienen una sola foto), o de varios días. Decidí que también así funcionaría mi galería en línea, y así es como reconstruí mi colección de fotos. Fue poco trabajo, en general, porque casi todo se pudo automatizar con scripts. Sólo tuve que reacomodar algunas fotos que no tenían un álbum bien definido, y de paso acomodé las fotos igual en la jerarquía del sistema de archivos: tengo un directorio 2009, dentro de él un directorio “02 Febrero”, y dentro de él un directorio para cada álbum, que como dije describen rangos continuos de tiempo.

Jerarquía de archivos

Jerarquía de archivos

La única bronca es cuando se me parte un evento que cae entre el último día de un mes y el primero del siguiente (los años nuevos suelen ser así), pero decidí que eso no era terriblemente grave. De esta forma, Shotwell no se mete para nada con mis fotos; jamás les escribe nada. Sólo lee información de ellas, y las despliega bonito, con lo que espero evitar las broncas que F-Spot me daba. Además, la organización de mis fotos en el disco duro es virtualmente idéntica a la organización de mis fotos en Shotwell.

Shotwell

Shotwell

Con mi colección reorganizada una vez más, decidí que necesitaba reestructurar mi galería en línea también. Inicialmente pensé en borrar las fotos que ahí estaban y meterlas todas de nuevo, pero resultó imposible: tuve que borrar todo y empezar de cero. Por suerte Gallery3 ofrece un API REST para manipular la galería en línea; está súper chido, muy fácil de programar, y me permite olvidarme de que nadie más me diga cómo deben subirse los datos a mi galería. Hice un programita en Python (versión 3; el uso de UTF-8 me impide que pueda usar Python 2) que le pasa uno una lista de archivos JPG, y les saca la información que me importa (básicamente la fecha, el título en Iptc.Application2.Caption y el álbum en Xmp.dc.subject), y procede como sigue:

  1. Saca el año de la foto, y comprueba que exista un álbum principal en la galería en línea llamado como el año. Si no existe, lo crea con la descripción “Año 2009”, por ejemplo.
  2. Saca el mes de la foto (01, …, 12), y comprueba que exista un álbum con ese nombre debajo del álbum del año correspondiente. Si no existe, lo crea con la descripción “Mes de Febrero”, por ejemplo.
  3. Saca el álbum de la foto, lo normaliza (quita acentos y símbolos, convierte espacios en guiones, etc), y comprueba que exista un álbum con ese nombre debajo del álbum del mes correspondiente. Si no existe, lo crea con la descripción idéntica al álbum, sin normalizar.
  4. Escala la foto a 1024×768, de ser necesario, preservando todas las etiquetas EXIF, IPTC y XMP.
  5. Sube la foto escalada al álbum correspondiente, usando como nombre el nombre del archivo, como título la etiqueta Iptc.Application2.Caption, y como descripción una vez más el álbum.

Todo esto es automático, y además el programa es suficientemente listo como para comprobar la existencia de cada álbum exactamente una vez; si ya comprobó que existe, guarda esa información para usarla en subsecuentes fotos. Además, si hay un error en la red lo detecta, y vuelve a subir la foto en ese caso. De los miles de fotos que subí, sólo me generó tres o cuatro duplicados, que además fue muy sencillo detectar. Mi programa incluso usa colorcitos para avisar qué está haciendo:

Mi programita en Python

Mi programita en Python

Las consecuencias de todo esto son varias: mi galería en línea tiene entonces una organización virtualmente idéntica a mis fotos en Shotwell (y por lo tanto en mi disco duro):

Mi galería en línea

Mi galería en línea

Pero además las fotos, durante todo este proceso, guardan la información siempre en ellas mismas; las tengo respaldadas (como ya he dicho) en varias máquinas en éste y otros sistemas solares, así que si algo terrible ocurriera con mi computadora y con mi galería en línea, no tengo nada de qué preocuparme: sólo copio mi respaldo, y puedo reconstruir mi colección en Shotwell casi de inmediato (sólo tengo que renombrar cada álbum, pero eso es muy rápido porque cada foto tiene una única etiqueta con el nombre de su álbum), y puedo reconstruir mi galería en línea de forma automática (aunque tendría que esperarme un rato a que acabaran de subirse las fotos).

Por supuesto, para que todo esto funcione las fotos en primer lugar deben tener la información dentro de ellas. Con la parte de mi colección que ya tenía organizada esto fue muy fácil, porque todo estaba en la base de datos SQLite de F-Spot. Con las fotos que no he organizado significa meterles el título (Iptc.Application2.Caption), y el álbum (Xmp.dc.subject). El álbum no me preocupa mucho, porque al cabo eso lo puedo hacer después de acomodarlas en directorios, y correr un script (que ya escribí), que lee el nombre del directorio (quitándole el prefijo numérico de ser necesario) y se lo pone a la foto.

El título es más desmadre, porque tengo que ponerlo foto por foto. Así que hice lo que cualquier programador que se llame así mismo uno haría: escribí un programa. Lo escribí en Vala (se me antojó después de ver el código de Shotwell), y de una vez le escribí el código necesario para que también pueda girar las fotos acostadas. Que de hecho no se giran, sólo se escribe una etiqueta EXIF que especifica que esa foto debe mostrarse girada.

Quick Photo

Quick Photo

El programa es bastante rápido; al dar Enter en el campo de texto, inmediatamente se guarda la información en la foto (que la imagen en sí no se modifica, sólo sus etiquetas), y se mueve a la siguiente. Lo único malo es que tengo que usar el ratón para girar la foto a la izquierda o derecha; tengo que programarle que lo pueda hacer desde el teclado, y entonces será casi perfecto para mis necesidades. Lo pienso liberar (junto con mi progamita en Python); a lo mejor a alguien le resulta útil.

Ahora sólo tengo que hacer lo que durante años estuve evitando: organizar las miles de fotos que no he organizado. Ya organicé (y subí) un par de meses de 2010; me falta el resto de ese año, el 2011 y el 2012. En 2012 no tomé casi fotos (estaba encerrado escribiendo la tesis, o quedándome sin novia, sin casa, sin dinero y sin trabajo), pero en 2011 tomé cientos de fotos en mi viaje por alrededor del mundo. Además, ya con esta infraestructura, supongo que debería también organizar las fotos de mi celular; varias lo valen, me parece, pero eso sí me va a tomar tiempo. Mientras tanto, tengo todo respaldado de forma redundantemente redundante, y cada vez que termino con un álbum nuevo (que generalmente se traduce a un día o dos de fotos), vuelvo a respaldar todos los cambios.

Mi punto con todo este impresionante choro, es que cuando creé mi galería en línea, el programa me pidió que le pusiera un nombre a la galería. Yo, falto como siempre de imaginación, le puse “Fotos de Canek”; así se sigue llamando hasta estos días. Pero después de ponerle el nombre, me pidió que lo describiera, y en ese momento (hace casi ocho años), sin pensarlo demasiado le puse “La vida a través de una cámara digital”.

Respaldando, reparando y reorganizando todas las fotos que he tomado desde abril de 2005, me di cuenta de que no pude haber elegido una mejor descripción para mi galería en línea: de verdad refleja mucho (aunque no todo, y muchas veces ni siquiera lo más importante) de lo que ha sido mi vida en estos años, que cubren básicamente mi maestría y doctorado hasta ahora. Los invito a que ustedes también le echen un ojo, si así lo desean, a mi renovada y mejorada galería en línea; pocas cosas me enorgullecen y alegran más que poder compartir las imágenes que capturan los varios momentos significativos que he tenido, y los viajes que he realizado.

Ahora que volví a revivir casi todos los momentos que fueron capturados con mi camarita Sony, no pude sino llegar a dos conclusiones: la primera, que soy increíblemente afortunado. Y la segunda: que me la he pasado poca madre en estos años. Incluso considerando los momentos amargos, las experiencias tristes, y las inevitables heridas del corazón, en retrospectiva para mí el balance es claro: lo bueno supera con creces, y por mucho, a lo malo. Me he divertido como enano en todo este tiempo.

Y lo bailado, nadie me lo quita.

Imprimir entrada Imprimir entrada

Los 500 GB

Hace unos meses se me murió un disco duro de 500 GB. No había nada ahí que no tuviera de forma redundante en otra máquina (y en algunos casos en varias otras máquinas), y lo que no estaba de forma redundante no era realmente importante para mí, o lo podía bajar de nuevo de Internet.

Todo, excepto varias decenas (si no centenas) de fotografías.

Mi galería en línea (la que acabo de actualizar, por cierto) la he tenido abandonada desde hace mucho tiempo. Regresé de mi primer viaje a Europa en marzo de 2009 con varias centenas de fotografías, y esto causó que me tardara mucho en catalogarlas y ordenarlas; mis subsecuentes viajes en 2010 agravaron la situación, y mi viaje monstruo de seis meses en el 2011 causaron que sencillamente dejara de pensar en que en algún momento de mi vida tenía que organizar mis fotos. Si lo hacía, terminaba tirado en el suelo en posición fetal respirando a través de una bolsa de papel.

Lo que terminó pasando fue que aventaba los nuevos archivos de mis fotos en varios directorios, y me olvidaba de ellos, sin jamás pensar “hey, tal vez sería buena idea respaldarlos”. Y entonces un día mi disco duro de 500 Gigabytes se murió, llevándose mi $HOME y otra partición que venía utilizando desde hacía años.

Esto ocurrió en las etapas finales de la escritura de mi tesis doctoral, así que no tuve tiempo de llorar por mis archivos perdidos. Mis documentos (incluyendo los de la tesis… y todos los demás) están triplemente respaldados en mi laptop, mi servidorcito Atom, y una máquina corriendo en una bóveda secreta debajo del océano Antártico, así que sólo cloné mis documentos en un $HOME vacío, y seguí escribiendo la tesis de doctorado.

No tiré el disco, porque pensé que tal vez un día existiría la posibilidad de recuperar la información en él.

Cuando acabé la tesis de doctorado, busqué en línea qué podía hacer para recuperar la información de un disco que estaba, aparentemente, morido. Debo resaltar que el disco duro en cuestión hacía los ruidos normales que hace un disco cuando se conecta y recibe poder, pero no aparecía en ningún lado: el BIOS de mi computadora no lo detectaba, y tampoco ocurría si lo conectaba por un cosito SATA→USB.

Internet no fue de gran ayuda, tal vez porque mi Google-fú no fue tampoco el mejor del mundo: básicamente pregunté “¿cómo le hago para revivir un disco duro morido?”. La respuesta en general fue que la mejor oportunidad (si no había click de la muerte), era agarrar el modelo del disco duro, y buscar en la red para comprar únicamente el PCB, el circuito (realmente una computadora chiquita) que está atornillado debajo. La idea era conseguir un reemplazo idéntico, y orar que con eso se pudiera recuperar la información.

Dado que ya entonces andaba corto de dinero, ni siquiera me pasó por la mente; por lo que leí había que pedir el famoso PCB a China, y nada más el envío iba a costar una lana, si es que acaso encontraba el modelo exacto que necesitaba, y sin ninguna garantía de que al final funcionara. Así que lo dejé pasar, y seguí con mi vida.

Que en este caso consistió en quedarme sin novia, sin casa, sin dinero y sin trabajo.

Cuando por fin me medio recuperé de eso, de las cosas que hice para distraerme fue actualizar y configurar propiamente mi media center. No recuerdo cuándo fue la última vez que platiqué de él, pero el año pasado lo cambié de Moovida a XBMC, que resultó ser de los programas más impresionantes en el mundo del software abierto que he visto en mi vida.

Organicé cuidadosamente mis series de televisión y mis películas, así como mi animé y mi música. Y cuando el polvo se asentó, resultó que el espacio libre en mis discos duros estaba peligrosamente bajo. Lo cual es obvio, porque había perdido 500 Gigabytes de espacio unos meses antes.

Cuando la situación se hizo insostenible, y al ver el ridículo precio al que han llegado los discos duros de 2 Terabytes, le hablé a Enrique y fuimos a que me comprara uno. El disco en sí mismo era necesario; pero además mi media center tenía un disco duro de 500 GB, y pensé que con suerte sería el mismo modelo del muerto, y por lo tanto que podría intercambiar los PCB de ambos, y milagrosamente recuperar mi información.

Así que el día que compré el disco de 2 Terabytes, agarré mi media center y lo abrí para cambiarle el disco. Cuando saqué el viejito, de inmediato vi que no me iba a servir para reparar el otro; son modelos distintos. Sin embargo, eso me hizo pensar que tal vez ahora no sería tan mala idea buscar el PCB del disco malo en la red, y eso hice.

Eso fue lo que debí hacer primero.

Resulta que ese modelo de disco duro en particular (de hecho de firmware) tiene un fallo muy conocido en la red: cuando un contador en el firmware llega a cierto número, el disco duro se apendeja y se queda ahí atorado en el estado “busy”, lo que hace que el problema sea mundialmente conocido como “Seagate BSY state”. Googléenlo, si quieren.

El punto es que en todos lados confirmaban que la información del disco estaba intacta, que era posible recuperarla, y de hecho que el mismo disco duro podía seguir siendo usado. El único problema es que necesitaba soldar un cable en especial para conectar el disco duro a un puerto serial, o canibalizar un cable de Nokia que ya tiene ese circuito integrado.

Hay una razón por la que no soy ingeniero: detesto estar peleándome con cablecitos. Por eso me ligué dos ingenieros el primer semestre de mi maestría para que con ellos pasara Arquitectura de Computadoras; yo me encargué de todo el software, y dejé que ellos se pelearan con el hardware.

Así que me puse a buscar el cable, un famoso Nokia CA-42, que para acabarla de amolar estoy seguro que tuve en algún momento de mi vida. En Amazon cuesta 7 dólares, pero no lo envían a México; en MercadoLibre está en 150 pesos, pero el envío cuesta otros 150. Así que me decidí ir al Centro a buscarlo. Lamentablemente sólo pude ir hasta hoy, porque estuve trabajando en un artículo hasta ayer en la noche. Y hoy es domingo, así que un montón de negocios estaban cerrados.

Por suerte lo encontré, pero vi con algo de temor que no era un cable de Nokia original, sino un clon chino, y ya había leído que a veces con cables no originales el procedimiento no servía. Como sea, lo compré (me costó 100 pesos), y regresé a mi casa. Voy a platicar muy por encima que es lo que tuve que hacer, pero pueden verlo a detalle aquí, aquí, o en el tubo, si les da hueva leer.

El disco duro seguía igual de muerto que meses atrás:

Disco duro muerto

Disco duro muerto

El PCB es lo que está debajo:

PCB

PCB

Lo quité:

PCB suelto

PCB suelto

Le puse un cartoncito encima de la cabeza, para que el PCB no se comunique con el disco duro, el mismo no responda, y entonces el firmware no entre en el estado ocupado:

Disco duro con cartón

Disco duro con cartón

Y volví a poner el PCB:

PCB con cartón

PCB con cartón

Después desmadré el cable de Nokia chafa, y lo conecté a un viejo cable para conectar CDs a tarjetas de sonido, para poder conectarlo fácilmente al disco duro:

Cable Nokia chafa

Cable Nokia chafa

Y finalmente prendí el disco duro, lo conecté por USB a la computadora con el cable, y comunicándome serialmente con él le mandé comandos para revivirlo. Pueden verlo a detalle en las páginas que ligo (y todo se puede hacer con Linux, sólo reemplacen Hyperterminal con minicom); la verdad no me importa mucho qué hicieran en particular los famosos comandos.

Lo que me importa es que funcionaron, y que recuperé mi disco duro.

Más importante aún es que toda la información en el mismo sigue ahí, y la estoy respaldando todita en este momento. Lo principal eran las fotos, pero de una vez estoy copiando todo, y a partir de ahora sí voy a mantener mis fotografías también triplemente respaldadas.

Y encima ahora tengo un disco duro extra de 500 GB. Y un cable Nokia chafa para hacer todo de nuevo, si acaso es necesario.

Imprimir entrada Imprimir entrada

Y el claro ganador es C

Ya no seguí con mis comparaciones entre C y Java porque, de verdad, he andado demasiado ocupado, con cosas académicas, de trabajo y personales. Pero hoy por fin terminé de escribir las pruebas unitarias para las estructuras de datos que estamos viendo en mi curso de ídem, y como la última que hemos visto fueron árboles rojinegros, decidí echarle un ojo a mis pruebas.

Para los que no lo sepan, los árboles rojinegros son árboles binarios autobalanceados, lo que en su caso particular quiere decir que un camino simple (sin regresarse nunca) de cualquier nodo a cualquiera de sus hojas siempre tiene el mismo número de nodos negros. Eso se traduce (por una propiedad de los árboles rojinegros que dice que un nodo rojo siempre tiene dos hijos negros) a que la diferencia más grande de longitudes entre ramas es que una tenga k nodos, y la otra 2k (una rama con puros nodos negros, otra con negro, rojo, negro, rojo, etc.) La estructura permite agregar y eliminar elementos con complejidad en tiempo acotada por la altura del árbol; que siempre esté balanceado garantiza que dicha complejidad es siempre O(log n). Los árboles rojinegros son una estructura de datos utilizada en todos lados por (como veremos ahorita) su espectacular desempeño; en particular, el kernel de Linux incluye una implementación desde mi punto de vista preciosa y humilladoramente elegante; la pueden checar en /usr/src/linux/lib/rbtree.c.

No voy a poner mi código para agregar o eliminar elementos a árboles rojinegros; no sólo porque mis alumnos aún no entregan su práctica, sino además porque es demasiado engorroso. No es ciencia de cohetes, pero sí hay suficientes casos como para que uno tenga que ir haciendo dibujitos en papel para no perderse en qué punto del algoritmo nos hayamos. Como sea, terminé de escribir el código en Java y C como siempre, y corrí unas pruebas con un millón de elementos (me pueden pedir el código, si quieren).

Ambas implementaciones le ganan por mucho a MergeSort; me imagino que algo tendrá que ver el uso de memoria (MergeSort crea el equivalente a log n listas con n elementos cada una durante la ejecución del algoritmo, mientras que los árboles rojinegros usan O(1) de memoria al agregar). Ambas son básicamente idénticas, incluyendo que usan recursión en el paso interesante: es recursión de cola, por lo que sencillamente lo pude haber reescrito iterativamente; pero como les digo el algoritmo ya es lo suficientemente engorroso como para que lo complicara aún más con un acumulador. La única diferencia discernible en el uso de cada versión, es que guardo la raíz cada vez que agrego en un elemento en C; tengo que hacerlo para no tener que andarla persiguiendo cada vez. En Java, al usar el diseño orientado a objetos, siempre tengo una variable de clase para la raíz.

Con Java, el algoritmo tarda 0.777676317 segundos (en promedio) en agregar 1,000,000 (un millón) de elementos. C sin optimizaciones tarda 0.376824469 segundos; con optimizaciones tarda 0.183850452 segundos. Por supuesto ambas versiones son genéricas; la de Java propiamente usando genéricos, la de C usando void* como tipo de dato, y pasando una apuntador a función para hacer comparaciones. Con 10,000,000 (diez millones) de elementos la diferencia es todavía más abismal; Java tarda 20.266719661 segundos, mientras que la versión en C tarda 1.881134884 segundos; pero esto ya no me extraña, dado que como ya había visto la última vez, con 10,000,000 de elementos, Java no puede evitar no utilizar el swap.

No me queda claro por qué C gana; dado que MergeSort también es recursiva, y ahí Java le ganaba a C, hubiera esperado que en el caso de los árboles rojinegros pasara lo mismo. Lo que sí es que el desempeño de la estructura de datos es espectacular, y a mí me parece de las estructuras más bonitas y poderosas que existen. Por supuesto los diccionarios (¿alguien sabe una mejor traducción para hash table?) también son muy padres; pero siempre está el hecho de que en el peor de los casos el buscar y el eliminar tardan O(n). Y como mis experimentos con QuickSort me recordaron hace unas semanas, el peor caso (o uno suficientemente malo) siempre anda asomándose detrás de las esquinas.

Imprimir entrada Imprimir entrada

Más sobre carreritas entre Java y C

Total que Omar me pidió mi código para hacer él mismo pruebas, lo que me obligó a limpiarlo un poquito. Entre las cosas que hice al limpiar mi código, fue cambiar cómo llenaba los arreglos y listas; originalmente los estaba llenando así (en Java):

import java.util.Random;
// ...
    Random r = new Random();
    // ...
    int n = r.nextInt() % 100;

Y así en C:

#include <stdlib.h>
// ...
    srand((unsigned int)time(NULL));
    // ...
    int n = rand() % 100;

El cambio que hice fue el reemplazar el número mágico 100 con N en el módulo al generador de números pseudo aleatorios, donde N es el número de elementos. Esto cambió radicalmente los resultados; para ponerlo en perspectiva, aquí están los resultados en C de una corrida (suelen ser todos muy similares) con módulo 100 (incluí el qsort de glibc a sugerencia de otro lector):

Tiempo QuickSort (normal): 46.125371409 segundos
Tiempo QuickSort (int): 6.318009789 segundos
Tiempo QuickSort (memcpy): 29.476040174 segundos
Tiempo QuickSort (long): 4.455134060 segundos
Tiempo QuickSort (glibc qsort): 0.182938334 segundos
Tiempo MergeSort (lista): 5.097989382 segundos
Tiempo MergeSort (dlista): 3.018067951 segundos

En Java los números son:

Tiempo QuickSort (int): 2.231362337 segundos
Tiempo QuickSort (genéricos): 35.452854731 segundos
Tiempo MergeSort: 2.599635738 segundos

Haciendo módulo N, los resultados son, en C:

Tiempo QuickSort (normal): 0.558278904 segundos
Tiempo QuickSort (int): 0.117254171 segundos
Tiempo QuickSort (memcpy): 0.279380050 segundos
Tiempo QuickSort (long): 0.121708671 segundos
Tiempo QuickSort (glibc qsort): 0.220501083 segundos
Tiempo MergeSort (lista): 5.311177622 segundos
Tiempo MergeSort (dlista): 3.196143267 segundos

Y en Java:

Tiempo QuickSort (int): 0.172914364 segundos
Tiempo QuickSort (genéricos): 0.578500354 segundos
Tiempo MergeSort: 2.15927644 segundos

Al inicio me súper saqué de onda, pero no tardé en encontrar la respuesta. Si N=1000000 (un millón), y cada entero en mi arreglo está entre 0 y 99 (inclusive), eso quiere decir que en promedio cada elemento del arreglo está repetido 10,000 veces, lo que hace que la probabilidad de encontrar un pivote malo (el mínimo o máximo del arreglo) sea mucho mayor que si estuvieran mejor distribuidos los valores. Es por ello que cuando cambio a hacer módulo N, todas mis versiones del algoritmo mejoran, algunas por varios órdenes de magnitud; en general el pivote es un buen pivote. Esta no es toda la historia, por supuesto; de ser así sólo tendría que elegir un pivote aleatorio y todo funcionaría más rápido, y como expliqué en la entrada anterior, en mis pruebas usar un pivote aleatorio no mejora sustancialmente el desempeño del algoritmo. Me parece que teniendo tantos elementos y tan poquitos valores (comparativamente) para inicializarlos, sencillamente ocurre muchas veces que un subarreglo tiene muchos elementos iguales, y entonces incluso un pivote aleatorio tiene mucha probabilidad de ser el mínimo o el máximo.

Sin embargo, esto no parece molestarle a qsort de glibc; es endiabladamente rápido. Incluso en mi versión módulo 100 corre en menos de medio segundo, y de hecho es la única implementación del algoritmo que lo hace. Como le comentaba al lector que me recomendó probar con qsort, la complejidad del mismo es más de un orden de magnitud mayor que mis versiones: qsort son unas 250 líneas de código no muy fácil de leer a comparación de 22 líneas en cada una de mis versiones. La complejidad radica en primer lugar en cómo encuentra el pivote: selecciona la mediana entre el primer, medio y último elementos del subarreglo (ordenándolos de paso); y en segundo lugar en que no hace recursión: utilizando una pilita se ahorra el estar gastando registros de activación, y mete todo dentro de un while.

No sé si sea eso lo que lo hace el ganador indiscutible de las distintas implementaciones; lo que sí sé es que glibc lo comenzaron a escribir a inicios de los ochentas, y que en estos treinta años le han metido todas las optimizaciones que se les han podido ocurrir. De cualquier forma, estoy ahora más contento con mis implementaciones en C: si el arreglo tiene sus valores bien distribuidos, cada una de mis versiones en C le gana a su equivalente en Java, y de hecho mi mejor versión genérica en C (que tiene exactamente la misma firma de qsort, por cierto) es sólo 0.05 segundos más lenta. Que no está nada mal, si me permiten decirlo.

De cualquier forma, los arreglos con muchos elementos repetidos son una entrada válida de QuickSort, así que Java sigue siendo bastante bueno en su versión para enteros (por cierto, mi versión genérica era desde el inicio mejor que la de Java; lo que pasa es que en Java el módulo incluye valores negativos, y entonces Java tenía el doble de valores distintos que la versión de C), y no terriblemente lento en comparación en las otras versiones. Para cosas genéricas C le puede ganar fácilmente; pero usando enteros nada más, Java le gana de calle a mi versión en C para arreglos con muchos elementos repetidos. Y de hecho implementando una versión iterativa de QuickSort, no dudo que Java le diera una buena pelea a qsort (un torito, para el que quiera animarse).

La otra conclusión importante es ver la enorme diferencia que puede significar el tipo de entrada para QuickSort; es por eso que Java utiliza MergeSort en general en sus algoritmos de ordenamientos. Es más lento en el mejor caso de ambos, sin duda; pero siempre es O(n log n). Además es estable (no cambia el orden de dos elementos iguales), lo que también está padre.

Dado que ya limpié el código gracias a Omar, lo pueden bajar de aquí. Dado que son implementaciones de una estructura y dos algoritmos que probablemente sean los más conocidos en el mundo, ni siquiera le pongo licencia a los archivos.

Imprimir entrada Imprimir entrada

Carreritas entre Java y C

Estoy enseñando Introducción a Ciencias de la Computación II (mejor conocida como ICC-2) por primera vez en mi vida, en gran medida por un ligero error administrativo. La verdad es que me estoy divirtiendo como enano (al parecer, tristemente disfruto yo más el curso que mis alumnos), y entre las cosas divertidas que decidí hacer fue el darle a mis alumnos la oportunidad de hacer las prácticas en C (en lugar de Java) por un punto extra durante el curso, o medio si hacen al menos la mitad. Desafortunadamente, ninguno de mis alumnos me ha entregado una práctica escrita en C.

Como sea, para poder dejarles las prácticas en C a mis alumnos, primero tengo que hacerlas yo, y eso es en gran medida la razón de que me esté divirtiendo tanto. Por supuesto también hago las prácticas en Java; como ICC-2 es en gran parte estructuras de datos, esto también significa ver las características novedosas de Java, como son iteradores y genéricos. Lo cual también es muy divertido; especialmente cuando puedo comparar los dos lenguajes en cosas como velocidad de ejecución.

Como es necesario siempre que uno ve arreglos y listas, les dejé a mis estudiantes que programaran QuickSort y MergeSort. Yo recuerdo que como estudiante tuve que programar esos algoritmos al menos tres veces: la primera en ICC-2, la segunda en Análisis de Algoritmos, y ahora sí que como dice la canción, la tercera por placer. También recuerdo claramente que QuickSort me parecía el mejor de ambos algoritmos; la inocencia de tener veinte años, supongo.

Total que implementé ambos algoritmos en C y en Java, y me llevé una sorpresa con los resultados. Voy a relatar lo que resultó de investigar porqué las diferencias en velocidades, que la verdad yo no termino de entender.

Aquí está QuickSort en Java:

public static void swap(T[] a, int i, int j) {
    if (i == j)
        return;
    T t = a[j];
    a[j] = a[i];
    a[i] = t;
}

public static < T extends Comparable < T > >
                 void quickSort(T[] a) {
    quickSort(a, 0, a.length-1);
}

private static < T extends Comparable < T > >;
                  void quickSort(T[] a, int ini, int fin) {
    if (fin - ini < 1)
        return;
    int i = ini + 1, j = fin;
    while (i < j)
        if (a[i].compareTo(a[ini]) > 0 &&
            a[j].compareTo(a[ini]) < = 0)
            swap(a, i++, j--);
	else if (a[i].compareTo(a[ini]) <= 0)
	    i++;
	else
	    j--;
    if (a[i].compareTo(a[ini]) > 0)
        i--;
    swap(a, ini, i);
    quickSort(a, ini, i-1);
    quickSort(a, i+1, fin);
}

Y aquí está en C:

inline static void
swap(void** a, int i, int j)
{
	if (i == j)
		return;
	void* t = a[i];
	a[i] = a[j];
	a[j] = t;
}

void
quicksort(void** a, int n, func_compara f)
{
	quicksort_aux(a, 0, n-1, f);
}

static void
quicksort_aux(void** a, int ini, int fin, func_compara f) {
	if (fin - ini < 1)
	    return;
	int i = ini + 1, j = fin;
	while (i < j)
		if (f(a[i], a[ini]) > 0 &&
                    f(a[j], a[ini]) < = 0)
			swap(a, i++, j--);
		else if (f(a[i], a[ini]) <= 0)
			i++;
		else
			j--;
	if (f(a[i], a[ini]) > 0)
		i--;
	swap(a, ini, i);
	quicksort_aux(a, ini, i-1, f);
	quicksort_aux(a, i+1, fin, f);
}

(En mi blog el código aparece bonito con destacamiento de sintaxis; no sé cómo aparecerá en RSS, pero dudo que bonito.)

Con el código así, la versión en Java necesita 33.4 segundos (en promedio en mi máquina) para ordenar un arreglo de un millón (1,000,000) de elementos aleatorios. Sin optimizaciones, la versión en C tarda 114.7 segundos; lo cual es una diferencia brutal, si me permiten decirlo. Con la mejor optimización (-O3; el resultado es idéntico a -Ofast), esta velocidad baja a 44.85 segundos; mucho mejor, pero de cualquier forma más lento que con Java.

La versión en C utiliza void** como tipo del arreglo, y recibe un apuntador a función f justamente para emular los genéricos de Java; la idea es que el QuickSort de C pueda ordenar arreglos de cualquier tipo de elemento. Nada más por completez, incluyo la definición del tipo func_compara, así como la implementación usada para estas pruebas:

typedef int  (*func_compara)      (const void*   a,
				   const void*   b);

int
compara_enteros(const void* a, const void* b) 
{
	int aa = *((int*)a);
	int bb = *((int*)b);
	return aa - bb;
}

Mi primera impresión fue que estos “genéricos” en C (altamente basados en la biblioteca GLib) le estaban dando en la madre a la velocidad de ejecución de mi implementación en C. El andar siguiendo los apuntadores, sacar el valor de las referencias en compara_enteros, y los castings probablemente eran la razón (pensaba yo) de que mi versión en C fuera (ligeramente) más lenta que la de Java. Así que hice trampa y volví a implementar QuickSort, pero esta vez nada más para enteros:

inline static void
swap_int(int* a, int i, int j)
{
	if (i == j)
		return;
	int t = a[i];
	a[i] = a[j];
	a[j] = t;
}

void
quicksort_int(int* a, int n)
{
	quicksort_int_aux(a, 0, n-1);
}

static void
quicksort_int_aux(int* a, int ini, int fin) {
	if (fin - ini < 1)
	    return;
	int i = ini + 1, j = fin;
	while (i < j)
		if (a[i] > a[ini] &&
                    a[j] < = a[ini])
			swap_int(a, i++, j--);
		else if (a[i] <= a[ini])
			i++;
		else
			j--;
	if (a[i] > a[ini])
		i--;
	swap_int(a, ini, i);
	quicksort_int_aux(a, ini, i-1);
	quicksort_int_aux(a, i+1, fin);
}

No muy sorprendentemente, esta versión le partió completamente su madre a la de Java: tarda en promedio 6.35 segundos. Hago notar que los elementos del arreglo son generados aleatoriamente; por lo que el escoger un pivote aleatorio entre ini y fin no serviría (en teoría) de nada. Ciertamente no marcó ninguna diferencia en mis pruebas.

Aunque esta versión es bastante rápida, estaba haciéndo muchísima trampa. De nada (o muy poco) me sirve un QuickSort rapidísimo, si voy a tener que reimplementarlo cada vez que cambie el tipo de mis arreglos. Así que me puse a pensar cómo mejorar una versión “genérica” en C. La respuesta es que sí se puede, pero es bastante feo desde mi punto de vista.

La idea es sencillamente utilizar aritmética de apuntadores, y al intercambiar elementos el copiarlos usando la memoria:

inline static void
swap_memcpy(void* a, int i, int j, size_t s, void* t)
{
	if (i == j)
		return;
	memcpy(t, a+(i * s), s);
	memcpy(a+(i * s), a+(j * s), s);
	memcpy(a+(j * s), t, s);
}

void
quicksort_memcpy(void* a, int n, size_t s, func_compara f)
{
	void* t = malloc(s);
	quicksort_memcpy_aux(a, 0, n-1, f, s, t);
	free(t);
}

static void
quicksort_memcpy_aux(void* a, int ini, int fin,
                    func_compara f, size_t s, void* t) {
	if (fin - ini < 1)
	    return;
	int i = ini + 1, j = fin;
	while (i < j)
		if (f(a+(i*s), a+(ini*s)) > 0 &&
                    f(a+(j*s), a+(ini*s)) < = 0)
			swap_memcpy(a, i++, j--, s, t);
		else if (f(a+(i*s), a+(ini*s)) <= 0)
			i++;
		else
			j--;
	if (f(a+(i*s), a+(ini*s)) > 0)
		i--;
	swap_memcpy(a, ini, i, s, t);
	quicksort_memcpy_aux(a, ini, i-1, f, s, t);
	quicksort_memcpy_aux(a, i+1, fin, f, s, t);
}

Esta versión es superior a la de void** ya que puedo pasarle un arreglo de tipo int* directamente, y funciona sin problema; la versión void** necesita por fuerza que le pase un arreglo de apuntadores al tipo que me interesa; en otras palabras, tengo que pasarle un int**, y además tengo que hacerle cast a void**. Además de esto (que no es poco), es más rápido que la primera versión en C, y más rápido que la versión en Java. No por mucho, pero más rápido: tarda 27.5 segundos con un millón de elementos.

Por lo demás, está bastante fea; necesito por fuerza el tamaño del tipo que me interesa ordenar (porque el arreglo lo veo como un chorizo enorme de bytes), y por lo mismo para intercambiar elementos del arreglo debo utilizar memcpy, además de que cargo por todas partes un pedazo de memoria t para guardar el valor temporal durante el intercambio; la alternativa hubiera sido usar variables globales (the horror!), o solicitar y liberar memoria en cada intercambio de variables.

Hasta aquí estaba más o menos satisfecho: ya tenía una versión “genérica” en C que era más rápida que la de Java (aunque desde mi punto de vista la solución sea bastante fea), pero entonces se me ocurrió que estaba siendo muy injusto: si hice una versión tramposa para C (la que sólo sirve para enteros), debería hacer una versión tramposa para Java también. Así que eso hice:

public static void swap(int[] a, int i, int j) {
    if (i == j)
        return;
    int t = a[j];
    a[j] = a[i];
    a[i] = t;
}

public static void quickSort(int[] a) {
    quickSort(a, 0, a.length-1);
}

private static void quickSort(int[] a, int ini, int fin) {
    if (fin - ini < 1)
        return;
    int i = ini + 1, j = fin;
    while (i < j)
        if (a[i] > a[ini] &&
            a[j] < = a[ini])
            swap(a, i++, j--);
        else if (a[i] <= a[ini])
            i++;
        else
            j--;
    if (a[i] > a[ini])
        i--;
    swap(a, ini, i);
    quickSort(a, ini, i-1);
    quickSort(a, i+1, fin);
}

Esta versión tarda 2.26 segundos en ordenar un arreglo de un millón de elementos, lo que la hace casi tres veces más rápida que la versión tramposa de C. ¿Por qué ocurre esto? Sinceramente, no tengo idea; lo único que se me ocurre es que con 1,000,000 recursiones, el compilador Just-In-Time (JIT) de Java alcanza a optimizar algo durante la ejecución del programa que la versión en C no puede. Yo no veo otra alterantiva; pero me encantaría oír teorías.

Sólo un pequeño dato para terminar con QuickSort; hice otra versión tramposa en C para el tipo long, y ésta corre en 4.35 segundos, lo que la sigue haciendo más lenta que la de Java, pero más rápida que la de enteros (int) en C. ¿A lo mejor porque mi máquina es arquitectura AMD64? Una vez más, no tengo idea; pero sí me gustaría saber qué carajo hace la JVM para ser tan rápida.

Los enigmas no terminaron ahí, porque también implementé MergeSort en Java y C. Primero les enseño mis estructuras de datos en ambos lenguajes; estas son mis listas en Java:

public class Lista< T > implements Iterable< T > {
    protected class Nodo< T > {
	public T elemento;
	public Nodo< T > siguiente;
	public Nodo< T > anterior;
    }
    protected Nodo< T > cabeza;
    protected Nodo< T > rabo;
    public void agregaFinal(T elemento) { ... }
    public void agregaInicio(T elemento) { ... }
    ...
}

Ignoren el Iterable; es sólo para poder recorrer la lista con el foreach de Java. Las listas en C siguen el modelo estructurado en lugar del orientado objetos; por lo tanto en C lidiamos con los nodos directamente (mientras en Java siempre están ocultos al usuario):

struct lista 
{
	void* elemento;
	struct lista* siguiente;
	struct lista* anterior;
};

struct lista* lista_agrega_final  (struct lista* lista,
				   void*         elemento);
struct lista* lista_agrega_inicio (struct lista* lista,
				   void*         elemento);

Por su puesto para su práctica mis alumnos tuvieron que implementar más cosas; pero nada de eso es relevante para lo que discuto aquí. Mi implementación de MergeSort en Java (para estas listas) fue la siguiente:

private static < T extends Comparable< T >> Lista< T >
    merge(Lista< T > li, Lista< T > ld) {
    Lista< T > l = new Lista< T >();
    Lista< T >.Nodo< T > nli = li.cabeza;
    Lista< T >.Nodo< T > nld = ld.cabeza;
    while (nli != null && nld != null) {
        if (nli.elemento.compareTo(nld.elemento) < 0) {
            l.agregaFinal(nli.elemento);
            nli = nli.siguiente;
        } else {
            l.agregaFinal(nld.elemento);
            nld = nld.siguiente;
        }
    }
    while (nli != null) {
        l.agregaFinal(nli.elemento);
        nli = nli.siguiente;
    }
    while (nld != null) {
        l.agregaFinal(nld.elemento);
        nld = nld.siguiente;
    }
    return l;
}

public static < T extends Comparable< T >> Lista< T >
    mergeSort(Lista< T > l) {
    int n = l.longitud();
    if (n == 1)
        return l;
    Lista< T > li = new Lista< T >();
    Lista< T > ld = new Lista< T >();
    int i = 0;
    Iterator< T > iterador = l.iterator();
    while (i++ < n/2)
        li.agregaFinal(iterador.next());
    while (i++ <= n)
        ld.agregaFinal(iterador.next());
	
    li = mergeSort(li);
    ld = mergeSort(ld);
    return merge(li, ld);
}

La versión en C es la que sigue:

static struct lista*
merge(struct lista* li, struct lista* ld, func_compara f) 
{
	struct lista* l = NULL;
	struct lista* ii = li;
	struct lista* id = ld;

	while (ii != NULL && id != NULL) {
		if (f(ii->elemento, id->elemento) < 0) {
			l = lista_agrega_inicio(l, ii->elemento);
			ii = ii->siguiente;
		} else {
			l = lista_agrega_inicio(l, id->elemento);
			id = id->siguiente;
		}
	}
	while (ii != NULL) {
		l = lista_agrega_inicio(l, ii->elemento);
		ii = ii->siguiente;
	}
	while (id != NULL) {
		l = lista_agrega_inicio(l, id->elemento);
		id = id->siguiente;
	}

	struct lista* tmp = lista_reversa(l);
	lista_libera(l);
	return tmp;
}

struct lista*
mergesort(struct lista* l, func_compara f)
{
	int n = lista_longitud(l);
	if (n == 1) {
		struct lista* uno =
                        lista_agrega_inicio(NULL, l->elemento);
		return uno;
	}
	struct lista* li = NULL;
	struct lista* ld = NULL;
	int i = 0;
	struct lista* tmp = l;
	while (i++ < n/2) {
		li = lista_agrega_inicio(li, tmp->elemento);
		tmp = tmp->siguiente;
	}
	while (i++ < = n) {
		ld = lista_agrega_inicio(ld, tmp->elemento);
		tmp = tmp->siguiente;
	}

	tmp = lista_reversa(li);
	lista_libera(li);
	li = tmp;

	tmp = lista_reversa(ld);
	lista_libera(ld);
	ld = tmp;

	tmp = ordenamientos_mergesort(li, f);
	lista_libera(li);
	li = tmp;

	tmp = ordenamientos_mergesort(ld, f);
	lista_libera(ld);
	ld = tmp;

	tmp = merge(li, ld, f);
	lista_libera(li);
	lista_libera(ld);
	return tmp;
}

Dado que una "lista" es realmente un nodo de la lista (siguiendo el modelo utilizado por GLib), no tengo guardado en nigún lado el rabo de la lista; por eso agrego elementos al inicio, y cuando termino la volteo. Hice mis pruebas de nuevo con 1,000,000 elementos, y lo primero que me sorprendió fue que fuera tan rápido en comparación con QuickSort; yo recordaba que cuando los implementé en mi carrera, la diferencia no era tanta. A lo mejor ahora programo mejor.

La versión en Java tarda (en promedio) 2.25 segundos; la versión en C 4.8, más del doble. Esta vez ya estaba preparado y no me sorprendió tanto, y de inmediato pensé que una obvia optimización es cargar el rabo de cada lista, y así poder agregar elementos al final en tiempo constante, sin tener que preocuparme de voltearla después. Para eso creé esta estructura de datos:

struct dlista {
	struct lista* cabeza;
	struct lista* rabo;
	int longitud;
};
void dlista_agrega_final(struct dlista* dl, void* elemento);
void dlista_agrega_inicio(struct dlista* dl, void* elemento);

Pude entonces simplificar mi versión de MergeSort:

static struct dlista*
merge(struct dlista* dli, struct dlista* dld, func_compara f) 
{
	struct dlista* dl = dlista_nueva();
	struct lista* ii = dli->cabeza;
	struct lista* id = dld->cabeza;

	while (ii != NULL && id != NULL) {
		if (f(ii->elemento, id->elemento) < 0) {
			dlista_agrega_final(dl, ii->elemento);
			ii = ii->siguiente;
		} else {
			dlista_agrega_final(dl, id->elemento);
			id = id->siguiente;
		}
	}
	while (ii != NULL) {
		dlista_agrega_final(dl, ii->elemento);
		ii = ii->siguiente;
	}
	while (id != NULL) {
		dlista_agrega_final(dl, id->elemento);
		id = id->siguiente;
	}

	return dl;
}

struct dlista*
mergesort(struct dlista* dl, func_compara f)
{
	int n = dl->longitud;
	if (n == 1) {
		struct dlista* uno = dlista_nueva();
		dlista_agrega_final(uno, dl->cabeza->elemento);
		return uno;
	}
	struct dlista* dli = dlista_nueva();
	struct dlista* dld = dlista_nueva();
	int i = 0;
	struct lista* tmp = dl->cabeza;
	while (i++ < n/2) {
		dlista_agrega_final(dli, tmp->elemento);
		tmp = tmp->siguiente;
	}
	while (i++ < = n) {
		dlista_agrega_final(dld, tmp->elemento);
		tmp = tmp->siguiente;
	}

	struct dlista* tmp2;
	tmp2 = mergesort(dli, f);
	dlista_libera(dli);
	dli = tmp2;

	tmp2 = mergesort(dld, f);
	dlista_libera(dld);
	dld = tmp2;

	tmp2 = merge(dli, dld, f);
	dlista_libera(dli);
	dlista_libera(dld);
	return tmp2;
}

Esta nueva versión corre en 2.72 segundos; mucho más cerca a la versión de Java, pero todavía más lenta. Lo único extra que se me ocurrió que podía hacer era eliminar el manejo de memoria; pensando que tal vez Java es más rápido (en este caso) porque puede diferir el liberar memoria hasta después de correr el algoritmo. Así que quité las llamadas a la función dlista_libera tratando de emular como sería tener recolector de basura, y por supuesto el algoritmo corrió ahora más lento: 2.92 segundos. ¿A lo mejor con 1,000,000 de elementos consigo forzar que Linux pase memoria al swap? No tengo idea; pero la verdad lo dugo: tengo 4 Gb de memoria, y no vi que el foquito de mi disco duro se prendiera.

Todos estos resultados pueden atribuirse a errores del programador (dícese, yo), pero honestamente no creo estar haciendo nada obviamente mal. Mi teoría favorita (y de hecho la única) es que el compilador JIT de la JVM está haciendo algo de magia que el simple ensamblador optimizado de C no puede; lo cual sería una muesta feaciente e innegable de las ventajas que pueden tener los lenguajes de programación que compilan para una máquina virtual altamente optimizada. Sumado a que es mucho más sencillo programar todas estas estructuras de datos si uno no tiene que preocuparse de manejar la memoria, y además con la fuerte (y desde mi punto de vista muy bonita) tipificación de datos que ofrecen los genéricos en Java, la verdad no vería por qué alguien escogería C sobre Java para programar cosas que tengan que repetir una misma tarea cientos de miles de veces.

Por supuesto es un experimento sólo en mi máquina, y en estos días 1,000,000 de elementos me suena a que ya no es realmente tanto. Con 10,000,000 de elementos, la versión en C tardó 36.32 segundos, y la versión en Java tardó 41.98 segundos; además de que tuve que aumentarle el tamaño al heap de la máquina virtual de Java a 4 Gb. Si lo aumentaba a 2 Gb, tardaba 54.99 segundos; en ambos casos el foquito de mi disco duro se prendió. En uso de memoria, sin duda C sigue siendo mucho superior (al costo de que uno tiene que andar manejándola a mano).

De cualquier forma, es impresionante lo rápido que es Java hoy en día; cuando yo lo comencé a aprender (el siglo pasado), todavía muchísima gente se quejaba de lo lento que era. Ahora yo (que no tengo poca experiencia programando) no puedo hacer que una versión en C del mismo algoritmo le gane.

En nuestras vidas profesionales lo más probable es que mis alumnos y yo no tengamos que implementar ninguno de estos algoritmos nunca; lo más seguro es que ya existirá una versión suficientemente buena y suficientemente optimizada disponible, lo que haría medio inútil que la implementáramos de nuevo. Sin embargo, me parece importante que un computólogo las implemente aunque sea una vez en su vida, y entienda cómo funcionan y cómo podrían utilizar una versión personalizada para algún obscuro problema que se encuentren.

Y ahora tengo que trabajar en mis algoritmos para árboles binarios.

Imprimir entrada Imprimir entrada

Extensiones

Isabel y yo tenemos cinco computadoras; dos laptops, mi computadora de escritorio, el servidorcito con procesador atom donde bajamos cosas, y mi media center. Si incluimos al PS3, son seis computadoras. Si incluimos el iPad de Isabel, son siete. Si incluimos nuestros teléfonos (que se puede argumentar que cuentan como computadoras), entonces son nueve.

Dado que seis de esos aparatos vinieron conmigo cuando me mudé con Isabel, no es de extrañar que el costo de la luz se haya incrementado por mucho (el horno de microondas, y mi tele de 46 pulgadas también deben influir). Eso implicó que empezáramos a hacer ciertos ajustes para disminuir nuestro consumo eléctrico.

El más obvio y sencillo fue el apagar algo cuando no se esté usando; lamentablemente es también el más incómodo en mi máquina de escritorio. Durante mi viaje de seis meses el año pasado, me acostumbré a trabajar en mi laptop, la cual nunca apago, a menos que le actualice el kernel o algo similar; en general sólo la suspendo, y entonces es básicamente instantáneo el regresar a trabajar: todas mis aplicaciones están abiertas tal cual antes de que la suspendiera.

Mi máquina de escritorio no tenía esto, y entonces sí es medio desesperante el prenderla y apagarla; no tanto por el tiempo que toma (es rápida al fin y al cabo), sino por el necesario esfuerzo de reconstruir mi estado de trabajo, dícese empezar mis aplicaciones y abrir mis documentos. Hoy por fin me puse a hacerle trutú a mi PC, y conseguí que suspendiera básicamente igual que mi laptop. La verdad me impresioné; nada más encontré la opción en el BIOS que lo permite, jaló de inmediato con Linux.

El problema es que suspender mi escritorio implica que tengo que dejarlo “prendido”. El CPU y discos duros están apagados, pero la máquina sigue consumiendo energía para mantener la memoria RAM activa, que es como funciona cuando uno la suspende. En la laptop no es tanto problema porque consume mucho menos electricidad que mi máquina de escritorio. Además, está el problema de que si se va la luz, pierdo todo (por lo anterior); en la laptop esto no pasa porque tiene batería.

Así que, después de años de no usarlo, investigué cómo hacer que mi máquina de escritorio hibernara. Hibernar es básicamente igual a suspender, sólo que los contenidos de la memoria van al disco duro en la partición del swap, y entonces la máquina se puede desconectar completamente. Al prenderse de nuevo, lo primero que hace Linux es restaurar los contenidos del swap a la memoria, y entonces todo regresa a como estaba. El proceso tarda un poco menos que prender la máquina de cero, y tiene la infinita ventaja de que el estado de la sesión de trabajo se preserva de modo perfecto; o en otras palabras, ya no tengo que iniciar mis aplicaciones ni reabrir mis documentos.

De todo ese choro no es esta entrada; la entrada es acerca de que GNOME no tiene una opción en su menú de estado para hibernar la máquina. Joder, con esfuerzos tiene una opción para apagarla; los genios de GNOME decidieron que todo mundo debería siempre suspender su computadora, y para poder apagarla uno tiene que presionar la tecla Alt para que la opción aparezca en el menú de estado.

Así que me fui al sitio de extensiones para el GNOME Shell, y busqué “hibernate”, y luego luego me salió la extensión Alternative Status Menu. Eso no tiene nada de sorprendente; lo ingenioso es que si visitan esa página con GNOME 3, aparece un botón para habilitar o deshabilitar la extensión. Así es, la pueden habilitar o deshabilitar desde el navegador (yo uso Chromium, pero al parecer funciona con Firefox y obviamente con Epiphany, el navegador de GNOME). Así que le hice click al botón, y de inmediato mi menú ganó la habilidad de apagar o hibernar sin tener que presionar teclas mágicas.

Menú de estado

Menú de estado

Por supuesto ya había leído al respecto, pero verlo funcionar sin que yo jamás hiciera nada sí me pareció sorprendente. No he jugado mucho con las extensiones del GNOME Shell (tengo sólo dos o tres), pero ésta es la primera que activo desde el sitio de extensiones, y la verdad estoy encantado con los resultados. De hecho, hasta me dan ganas de programar unas cuantas yo mismo.

Como sea, ya no tengo que preocuparme de restaurar el estado de mi escritorio cada vez que apago mi máquina; y si sólo dejo de trabajar un rato (porque voy a comer o algo así), puedo sólo suspenderla.

Y se quejaban de que GNOME 3 no ofrece suficientes opciones.

Imprimir entrada Imprimir entrada

Habría que portarlo a mi Xperia Play

Hace unos días reportaron en Slashdot que el código fuente de Prince of Persia había sido liberado en Github.

Casi de inmediato, cloné el repositorio y le eché un ojo al código. Resulta ser una instancia sorprendentemente limpia de ensamblador para la Apple ][, que por supuesto yo jamás ni siquiera había visto; los únicos ensambladores que conozco son el de Sparc clásico, muy poquito de x86, y el que nos inventamos para nuestro procesador con números complejos en mi curso de Arquitectura de Computadoras en la maestría.

Dicho eso, todos los ensambladores se parecen (aunque los geeks de hardware pongan el grito en el cielo al oír eso y digan que RISC y CISC son fundamentalmente distintos), y he podido seguir el código, que es obviamente muy corto: 30,000 líneas de ensamblador (con comentarios incluidos). El juego corría de un sólo disco de 5¼ pulgadas, al fin y al cabo.

No creo poder hacer nada con el código, y menos ahorita con tan poquito tiempo, pero estaría padre que portaran el juego a mi Xperia Play (aunque puedo jugar la versión para NES en un emulador). A casi 25 años de que fue creado, sigue siendo uno de los mejores juegos que se han hecho, me parece.

Qué chido que lo liberaron.

Imprimir entrada Imprimir entrada

Cómo has cambiado

Linux ya no es lo que era antes.

Isabel necesita escanear unas cosas y, por una serie de circunstancias que me niego a relatar, trajo un escáner de sus papás al departamento. Yo inmediatamente me puse a saltar como niño con juguete nuevo, pensando en toda la sana diversión que obtendría de estar tratando de echar a andar el (aparentemente) chafísima escáner USB.

Lo conecté, y funcionó de inmediato. Qué desilusión.

Cómo han cambiado las cosas en quince años; en 1997 tuve que recompilar mi kernel (por primera vez en mi vida) para que jalara mi tarjeta de sonido en Linux. Ni siquiera recuerdo para qué; dudo que yo supiera de la existencia de MP3s en ese entonces.

Ahora uno conecta las cosas, y funcionan. Qué aburrido.

Imprimir entrada Imprimir entrada

Trucos con \LaTeX

Estoy escribiendo mi tesis de doctorado, y lo estoy haciendo en español porque al fin y al cabo los artículos sobre los que estará basada ya los escribí en inglés, y no le veo sentido a andarme rompiendo la cabeza de nuevo escribiendo en inglés cuando puedo hacerlo en español, y soy mucho mejor escritor en mi idioma natal.

Como sea, escribiendo \LaTeX en español de nuevo ha hecho que descubra (o redescubra) varios trucos interesantes. Lo primero es hacer que \LaTeX hable español, por supuesto, que se logra con un simple

\usepackage[spanish]{babel}

Lo siguiente es hacer que \LaTeX use UTF-8 para entender acentos, para así poder escribir á, y no \'a. Yo esperaría que ya todo mundo lo supiera, pero me he encontrado con varias personas que siguen usando el modo “tradicional”, que es por supuesto lento, propenso a errores, y en español hace que un documento sea ilegible. Para que \LaTeX use UTF-8, sólo se necesita un simple

\usepackage[utf8]{inputenc}

Con eso al 99% de la gente que escribe \LaTeX en español debería bastarle; para los neuróticos como yo, el que sigue está interesante. Con los dos paquetes de arriba \LaTeX ya genera un documento correcto usando español, pero si uno usa \textrm{pdf}\LaTeX (como yo, que ya le perdí la fe para siempre a PostScript), en el documento resultante no están sincronizados el texto dibujado en pantalla, y el texto subyaciente. Para que me entiendan, creen un documento \LaTeX con los dos paquetes que mencioné, compílenlo con \textrm{pdf}\LaTeX, y luego seleccionen una parte del documento con acentos. Debería pasarles algo así:

Texto seleccionado

Texto seleccionado

Eso no sólo se ve horrible; la búsqueda en el PDF deja de funcionar, y afecta también cosas como buscadores automáticos (como el de Google) que analizan los PDFs por el texto subyaciente, no por cómo se dibuje en la pantalla. Repararlo es bien sencillo:

\usepackage[T1]{fontenc}

Con este paquete, el PDF ya sincroniza el texto subyaciente con el dibujado en la pantalla, y todos los problemas que mencioné arriba se corrigen:

Texto seleccionado con fontenc

Texto seleccionado con fontenc

El siguiente truco está relacionado; para las tesis en la UNAM, la portada siempre tiene que seguir un cierto formato del que sencillamente no hay forma de escapar. La manera más sencilla (para mí) de cumplir con el requerimiento de la portada, fue hacerla en Inkscape, exportarla a PDF, e incluirla como página completa en mi documento con \includepdf, del paquete pdfpages. Ahora, todo el texto en la portada lo hice con \LaTeX dentro de Inkscape para que usara la misma fuente que el resto del documento, para esto usé la extensión textext de Inkscape que permite insertar la salida de \LaTeX como SVG dentro de un archivo de Inkscape (que también es SVG).

Todo esto funciona muy bien, pero como el texto de \LaTeX se inserta como SVG (dícese, líneas, curvas de Bézier, y cosas así), el PDF resultante no tiene texto subyaciente, y por lo tanto no es seleccionable, buscable, analizable, etc., porque de hecho no hay tal. Para arreglarlo es muy fácil; uno toma su documento en Inkscape:

Documento en Inkscape

Documento en Inkscape

Y le agrega texto de Inkscape, o sea, el texto que de hecho SVG sabe manejar:

Documento en Inkscape con texto

Documento en Inkscape con texto

Por supuesto, uno selecciona la fuente de Inkscape que mejor se acerque a la de \LaTeX aunque dado el cuidado que pone \LaTeX para dibujar texto, por mucho que se parezca la fuente de Inkscape no se verá igual (que es la razón por la cual uso texto de \LaTeX y no de Inkscape en primer lugar). Hecho esto, uno centra el texto de Inkscape sobre el de \LaTeX, para que estén casi uno encima del otro:

Documento en Inkscape con texto centrado

Documento en Inkscape con texto centrado

Y por último uno selecciona el texto de Inkscape, y lo hace invisible:

Documento en Inkscape con texto invisible

Documento en Inkscape con texto invisible

Y ya, con esto el texto subyaciente del PDF será el de Inkscape, y aunque no se verá idéntico al dibujado en el PDF, sí será seleccionable:

PDF seleccionable

PDF seleccionable

Por supuesto, cuando no se esté seleccionando, el texto de Inkscape será invisible, dejando únicamente visible (e imprimible) el texto bonito de \LaTeX. Ya que tuve mi portada lista, lo siguiente que pensé fue si valía la pena hacer las figuras de mi tesis seleccionables. Son chorroscientas, y además tendría que estar poniendo caracteres griegos la mayor parte del tiempo, y eso me dio mucha flojera. Sin embargo, hice una pequeña extensión para Inkscape que toma el texto de \LaTeX generado por textext, e inserta el código \LaTeX que generó el texto. Por ejemplo, la siguiente figura:

Figura

Figura

esto es lo que se ve cuando uno selecciona texto \LaTeX dentro de ella:

Figura seleccionable

Figura seleccionable

No es terriblemente útil, pero como ligué mi extensión en Inkscape a un atajo del teclado, es bastante fácil de hacer, y se ve mamón. Esos son todos los trucos que he aprendido (o vuelto a aprender); si encuentro otros luego los publico.

Imprimir entrada Imprimir entrada

El código portátil

En Barcelona estuve trabajando con un investigador, Carlos Seara, y entre las varias cosas que hice fue el escribir un programita que utilizamos para ver ejemplos y cosas por el estilo. Desde hace años he venido juntando pequeños pedazos de código en Python que me permiten hacer programas de cosas geométricas (sólo en 2D) más o menos rápido; esos pedazos de código son lo único que queda de mi programa de geometría, geom, que lo tengo abandonado desde hace años. Como sea, el programita que hice en Barcelona está escrito en Python y, aunque funcional, ciertamente es muy sencillo:

RLayers en Linux, versión simple

RLayers en Linux, versión simple

Justo antes de salir de Toronto, Carlos me dijo que expondrá acerca de lo que trabajamos en noviembre, y que sería bueno que pudiera mostrar el programa durante su presentación. Dado que él usa Windows, me dijo que, si podía, le enviara una versión que jalara ahí.

Hay dos cosas que de verdad detesto en esta vida; una de ellas es Windows, y otra es tener que trabajar en Windows, así que no estaba muy emocionado con la idea, pero comencé a ver qué podía hacer. Yo tengo Windows Vista instalado en mi laptop (venía con la máquina), y para lo único que lo he utilizado en todo el tiempo que la he tenido es para actualizar mi teléfono Xperia Play (voy a creer que los idiotas de Sony-Ericsson no puedan hacer un programa para Linux); pero definitivamente no quería abandonar mi hermoso escritorio GNOME 3.2 para menearle cosas a Windows.

Definitivamente no: eso hubiera sido demasiado fácil.

En su lugar, hice lo que cualquier persona sensata no haría, e instalé Windows XP en una máquina virtual (afortunadamente los alumnos de la UNAM tenemos acceso a casi todas las versiones de Windows gratis). Hace unos años VMWare era la máquina virtual que todo el mundo usaba, pero desde hace algún tiempo le viene haciendo mucha competencia VirtualBox, que es de Oracle (antes Sun). VirtualBox tiene además la no despreciable ventaja de que es Software Libre.

Por supuesto VirtualBox viene con un interfaz gráfica que le permite a uno fácil y rápidamente preparar su máquina virtual dando unos cuantos botonazos del ratón. Pero, una vez más, eso sería demasiado fácil; la interfaz gráfica utiliza Qt, que yo no instalo en mis computadoras sino bajo la más absoluta emergencia. Por “suerte” VirtualBox se puede instalar sin interfaz gráfica, y configurar y echar a andar usando únicamente la línea de comandos.

Si les pudiera explicar toda la sana diversión que tuve haciéndolo. Como sea, eventualmente por fin tuve mi máquina virtual corriendo dentro de mi Linux:

Windows XP en VirtualBox

Windows XP en VirtualBox

Entonces ahí empezó la diversión en serio. Mi programa está escrito en Python, y utiliza básicamente Gtk+ y Cairo, con la bola de dependencias que esas dos bibliotecas se jalan. En teoría, un programa escrito en Python con Gtk+ y Cairo debería ser portátil a Windows sin muchas dificultades. En la práctica, uno tiene que estarle meneando a varias cosas en el código para que sea, bueno, portátil.

La verdad no fue tan difícil: uno básicamente instala Python para Windows, y luego instala un paquete cómodamente hecho de antemano que contiene Gtk+, sus bindings para Python, e ídem con la bola de dependencias necesarias. La cosa tiene el tino de llamarse all-in-one install. Con eso, mi aplicación ya corría en Windows (XP, al menos):

RLayers en Windows XP, versión simple

RLayers en Windows XP, versión simple

Aquí yo podría haber dicho “misión cumplida”, enviarle las instrucciones a Carlos, y dejar que sufriera lentamente instalando cosas en Windows (una experiencia miserable para cualquier persona). Pero no, decidí que iba a enviarle algo sencillo de instalar, y que de paso iba a mejorar el programita.

Así que de regreso en Linux me puse a mejorar el programita: le creé su interfaz gráfica en Glade, le puse menúes y barra de herramientas, le creé un formato para los archivos que utiliza, y pendejaditas de ese estilo. Ya a punto de acabar caí en cuenta de que probablemente correría en Español el programa, así que además le agregué soporte para gettext, para que pudiera hablar distintos idiomas:

RLayers en Linux, versión bonita

RLayers en Linux, versión bonita

Por supuesto, el chingado soporte para idiomas valió madre, porque en Windows gettext junto con PyGTK hace cosas muy extrañas. Al parecer hay forma de solucionarlo, pero involucran magia negra, así que me lavé las manos y forcé al programa a hablar inglés siempre. Como sea, ahora se veía bonito en Windows XP también:

RLayers en Windows XP, versión bonita

RLayers en Windows XP, versión bonita

Pero todo esto no eran más que manitas de gato: lo interesante era lograr que la chingadera fuera “fácilmente” instalable. Así que investigué y resultó que lo que había que hacer era utilizar un módulo de Python (en Windows) que “automágicamente” arma un ejecutable con todo lo necesario dentro para que el programa funcione, supuestamente ya sin necesidad de instalar nada más. El módulo es py2exe, y tiene una de las peores documentaciones que he visto, así que me tuve que basar en Google para saber exactamente qué tenía que hacer.

La cosa se volvió básicamente una pesadilla porque soy muy neurótico en muchas cosas: por ejemplo, el programa sigue siendo el mismo para Windows y para Linux, y el archivo de instalación (del cual se cuelga py2exe para hacer su magia) también funciona en los dos sistemas operativos. También la máquina virtual es bastante lenta (mi laptop no es muy poderosa para empezar), y eso causaba todavía más quebraderos de cabeza.

Pero por fin, después de muchas horas de dolor y sufrimiento, la cosa funcionó dentro de mi máquina virtual.

RLayers en Windows XP, de un ejecutable

RLayers en Windows XP, de un ejecutable

Ya teniendo todo esto hecho, lo único que faltaba era la prueba final: ver que funcionara en mi Windows Vista, el que no es virtual. Este paso es fundamental, porque el ejecutable de mi máquina virtual podía utilizar bibliotecas no incluidas por py2exe, y la única forma de comprobar esto es ejecutándolo en una máquina donde no se haya instalado Python, ni Gtk+, ni nada relacionado.

Así que cruzando los dedos reinicié a Windows, y por un milagro de esos que ocurren de vez en nunca, la maldita cosa funcionó sin problemas.

RLayers en Windows Vista, de un ejecutable

RLayers en Windows Vista, de un ejecutable

En mis planes originales estaba hacer un instalador, pero acabé realmente hasta la madre, y yo creo que Carlos no tendrá problemas en descomprimir un archivo Zip, y darle doble click a un ejecutable.

Ahora no quiero saber nada de Windows (ni de máquinas virtuales) en un buen rato.

Imprimir entrada Imprimir entrada

Apple sin cabeza

Ayer se murió Steve Jobs, como todo mundo sabe, y al inicio pensé en no escribir nada al respecto. Pero como todo mundo lo está haciendo y, además, todo mundo al parecer sigue cayendo en esta costumbre detestable de canonizar a alguien nada más pasa a pasar del estado vivo al estado morido, decidí escribir algunas palabras al respecto.

Steve Jobs no cambió mi vida. Al menos no directamente; nunca tuve una computadora de Apple, y de hecho nunca he tenido ningún producto de esa compañía, exceptuando un iPod Shuffle que compré en el 2005, y que nunca me gustó mucho realmente. Y dicho iPod nunca lo conecté a iTunes: lo usé con libgpod, cuando lo usaba, que con el paso del tiempo fue cada vez menos y menos veces. Y hablando de software, nunca tampoco usé software producido por Apple: a lo más algunas veces QuickTime, y siempre me produjo más dolores de cabeza que otras cosas.

Así que no, Jobs no cambió mi vida. Que el tipo “cambió” el mundo de la tecnología es innegable, por supuesto: pero lo cierto es que Jobs, hasta donde yo tengo entendido, jamás creó por sí mismo nada. Desde los inicios de Apple, cuando Steve Wozniak era el que le meneaba a la parte tecnológica, Jobs nunca fue realmente un creador: sabía muy bien cómo explotar las creaciones de otras personas, y sabía todavía mejor cómo presentarlas y vendérselas al mundo. Mucha gente habla de su “visión”, como si a Jobs realmente se le hubiera ocurrido algo nuevo alguna vez en su vida, cuando la triste verdad es que tomaba la tecnología existente y se la empacaba al usuario para que tuviera una experiencia sencilla, limpia, y sin duda alguna con mucho estilo. De creador no le reconozco nada a Jobs; de alguien que sabía muy bien cómo empaquetar tecnología de tal forma que fuera sencilla y divertida de usar, le reconozco todo.

Mucha forma, poco contenido.

A mí en particular nunca me atrajeron las Mac, los iPods (excepto mi Shuffle, que realmente me decepcionó), los iPod Touch, los iPhones o los iPads. El hardware, aunque sin duda alguna bonito, me pareció siempre excesivamente caro para lo que ofrecía. El software, aunque sin duda alguna bonito, era cerrado, así que de entrada jamás me llamó la atención.

Por supuesto, bien por aquellos que se orgasmeaban (y orgasmean) con los productos de Apple: cada quién lo suyo. Sólo estoy explicando por qué a mí nunca me llamaron la atención.

Y con Steve Jobs en particular nunca sentí (ni siento) nada negativo hacia él: jamás me hizo nada, que yo sepa. Sólo no le voy a asignar el epíteto de genio o visionario, cuando lo que fue realmente fue sólo un muy buen vendedor. Un muy buen merolico; tal vez el mejor de este inicio de siglo. Pero merolico al fin y al cabo.

No lamento su muerte más de lo que lamento la muerte de cualquier ser humano que, en general, no haya sido un hijo de la chingada (aunque al parecer eso era con sus empleados); y ciertamente tampoco me alegra, ni mucho menos. Pero tampoco creo que yo, en particular, ni el mundo, en general, tenga nada que agradecerle. El tipo hizo productos, y como buen merolico salió a la calle a venderlos, y fue endiabladamente exitoso al hacerlo. Tan exitoso que se pudrió de dinero haciéndolo (aunque al final ni todo el dinero del mundo lo salvó del cáncer, aunque con él se compró siete años más de vida).

No le dio (en el sentido de obsequiar) nada al mundo. Vender sí, vendió muchísimo, y fue inclemente con su competencia para que ellos no pudieran vender (vean la demanda de Apple contra Samsung en Alemania, por ejemplo), pero no dio nada. Soltó algo de dinero a beneficencias y cosas del estilo, pero por ejemplo Bill Gates ha dado muchísimo más, y además es algo que hacen casi todos los millonarios en el mundo.

Jobs no creó curas, no creó conocimiento, no creó arte (y no, aunque él era la cabeza de Pixar, una vez más él no era el creador ahí), no creó (para motivos prácticos) tecnología. Sólo vendió, y vendió, y vendió. Y está bien: así funciona el capitalismo, Jobs fue de los pocos afortunados que sin crear por él mismo algo nuevo se hinchó de lana de forma ridícula. Bien por él; incluso podría yo estar de acuerdo en que se lo merecía.

Pero por favor no me vengan conque hay que estarle agradecido por nada.

Se murió; eso está gacho. Particularmente porque dicen que el cáncer de páncreas (que me parece es el que tenía) es de los más dolorosos que existen. Sinceramente, qué mala onda por su familia y por sus seres queridos.

Pero el mundo no perdió más que un excelente vendedor. No es como cuando murió Gandhi, o Einstein, o ni siquiera John Lennon (que en su corta vida creó muchísimo más en arte que Steve Jobs).

Así que me parece pertinente algo de mesura. Murió un ser humano, uno muy famoso, y con un especial talento que utilizó de forma perfecta para volverse millonario. Como con cualquier vida humana terminada antes de tiempo, es una tragedia. Pero sólo anteayer murieron más de 70 personas en Mogadiscio por una bomba; hace tres días encontraron cinco cabezas en México.

Si alguien en México se lamenta más por la pérdida de Steve Jobs, que por los 40,000 muertos relacionados con el narco que han habido en el país durante el “sexenio” del animal que usurpó Los Pinos en el 2006, me parece que sinceramente deberían reconsiderar sus prioridades.

Imprimir entrada Imprimir entrada

GNOME 3.2

GNOME 3.2 tuvo a bien salir este 28 de septiembre. Como estaba en Toronto y faltaban dos días para que yo volara a California, hice lo que cualquier persona sensata hubiera hecho.

Lo instalé de inmediato.

Por supuesto esto causó que el escritorio en mi laptop tuviera a bien morirse un rato, pero es el tipo de cosas que uno debe dar por sentado si planea hacer pendejadas de este estilo. Después de menearle tantito por fin lo compuse, y ahora está jalando bastante bien, aunque tuve que desactivar mis extensiones de System Monitor y Weather. Las dos son básicamente inútiles: aunque me gusta tener la información de la carga de mi sistema, y qué temperatura hace afuera, no las necesito para nada. Y seguramente las portarán a 3.2 en unos días o semanas. También tuve que desactivar mi extensión Auto Move Windows, pero esa sí me duele, porque me parece utilísimo poder decirle a mi laptop en qué escritorio quiero qué aplicaciones. Esa también la portarán pronto, espero.

Como sea, Slashdot y OSNews sacaron las historias correspondientes, que yo no había leído por tener mi máquina desconchavadita, y por andar volando a gringolandia. Hoy las leí, y me impresionó el odio que mucha gente sigue expresando contra esta nueva versión de GNOME. Eso lo entiendo; como he comentado muchas veces, siempre es así en Linux cuando nuevas tecnologías se introducen.

Lo que no entiendo es las acusasiones de que GNOME 3 baja la productividad de alguien. Ahora que estuve escribiendo artículos en Toronto, mi laptop la usé de hecho para trabajar, y no sólo para divertirme como normalmente hago. Y si algo me impresionó de GNOME 3, es cómo ayuda a trabajar. Especialmente la capacidad de casi no necesitar usar el ratón nunca para iniciar aplicaciones y cambiar entre ellas. Que las notificaciones aparezcan abajo y sin llamar mucho la atención es maravilloso, especialmente para gente como yo que se distraen porque vuela la mosca.

Casi todas las críticas que he leído son “reparables” (entre comillas, porque me parece que es una cuestión más de gusto que de mal diseño) con extensiones, y además me extraña que más gente no se percate de lo que las mismas harán por GNOME. Como comentaba hace unos días, configuré Emacs para que se hablara con Evince y pudiera saltar del editor al visor y de regreso (lo cual es endemoniadamente útil al estar escribiendo \LaTeX… pero no “configuré” Emacs realmente. Lo extendí usando Emacs Lisp.

Con un GNOME 3.2 extensible vía JavaScript va a ser lo mismo, sólo que usaremos diccionarios (hashes) en lugar de listas. Los desarrolladores de GNOME están tratando de restringir lo que las extensiones podrán o no hacer, para que no presenten un problema de seguridad para usuarios “normales”: pero para power users como yo dichas restricciones serán completamente irrelevantes, y podremos extender GNOME a que haga todo lo que queramos. Yo estoy por ejemplo pensando en una extensión para escribir entradas en mi blog (regresé a usar el navegador porque Drivel comenzó a hacer cosas que no me gustaban con el HTML de mis entradas), y a lo mejor hacer dicha extensión a su vez extendible, para lidiar con casos como el mío, que las imágenes que agrego en mi blog siguen un formato muy específico.

Es uno de miles ejemplos que me vienen a la mente: JavaScript es al fin y al cabo Turing completo, y (como quince millones de veces más importante) completamente interpretado. Lo que quiere decir que escribir, probar y actualizar extensiones será tan trivial como aventar un archivito .js en un directorio y luego modificarlo.

Esa es la idea, me parece, que están persiguiendo los desarrolladores de GNOME: el caché que va a tener ahora el escritorio vendrá de la cantidad de extensiones que tendrá disponible para hacer pendejaditas. No hablo de las aplicaciones, que esas básicamente están cubiertas: hablo de extensiones que estarán completamente integradas al escritorio, incluyendo el look and feel, y que únicamente funcionarán para GNOME.

Se ve divertido el futuro.

Imprimir entrada Imprimir entrada

Y ahora de regreso también

Hace poco más de un mes, describí cómo configurar Emacs para ligarlo a Evince, de tal forma que si compilamos un archivo \LaTeX a PDF con la opción -synctex=1, y al hacer Control-click en una parte del PDF, Emacs enmarque el archivo .tex en la línea correspondiente.

A los pocos días Omar me comentó que sí servía, y se quejó amargamente de que no funcionaba al revés: que dentro de Emacs mi código (que estaba basado en en el de aquí) no permitía saltar dentro del PDF a la región de texto correspondiente al archivo .tex.

Por supuesto, una vez más, sí se puede: estamos hablando de Emacs al fin y al cabo. Sólo que yo estaba atareadísimo terminando de escribir un artículo y las notas para otro, y los fines de semana yendo a la CN Tower y a las cataratas del Niágara, y no había tenido tiempo de revisar el código. Además, es Emacs Lisp, que la verdad (como todos los lenguajes tipo Lisp) tiendo a aborrecer ligeramente.

Por fin hace unos días revisé el código, y lo primero que hice fue corregir y mejorar algunas cosas de la primera parte, lo que hace que Evince se comunique con Emacs. El código funcionaba porque el alcance de las variables en Emacs Lisp no tiene sentido; en un lenguaje más sensato hubiera fallado miserablemente. Corregí eso y así quedó:

(require 'dbus)

(defun goto-line-and-recenter (line col)
    (goto-line line)
    (recenter line)
    (raise-frame))

(defun synctex-find-file (buf line col)
  (find-file buf)
  (goto-line-and-recenter line col))

(defun synctex-switch-to-buffer (buf line col)
  (switch-to-buffer buf)
  (goto-line-and-recenter line col))

(defun evince-backwards-sync (file linecol time)
  (let ((buf (get-file-buffer (substring file 7)))
        (line (car linecol))
        (col (cdr linecol)))
    (if (null buf)
      (synctex-find-file (substring file 7) line col)
      (synctex-switch-to-buffer buf line col))))

(dbus-register-signal
 :session nil "/org/gnome/evince/Window/0"
 "org.gnome.evince.Window" "SyncSource"
 'evince-backwards-sync)

Quedó un poquito más corto y más bonito; aunque en GNOME 3 sigue sin levantar la ventana de Emacs cuando se enmarca el documento (otra queja amarga de Omar). En otros escritorios debería levantarla: no tengo acceso a otros escritorios ahorita, y aunque lo tuviera la verdad me da mucha hueva comprobarlo.

Después comencé a ver el otro lado, que Emacs se comunique con Evince, y resulta que es similarmente sencillo, exceptuando el hecho de que Emacs Lisp es de esos lenguajes idiotas que dicen ser débilmente tipificados, lo cual significa que los tipos fallan justo cuando uno no quiere que fallen. Siendo justo, el problema realmente es que DBus es fuertemente tipificado, y entonces a veces hay que darle una manita a Emacs Lisp para que sepa cuál es el tipo que debe enviar por el cable (de ahí los feos :int32 que de repente aparecen en el código).

El código correspondiente me quedó así:

(defun get-evince-document (file)
  (dbus-call-method
   :session "org.gnome.evince.Daemon" "/org/gnome/evince/Daemon"
   "org.gnome.evince.Daemon" "FindDocument"
   (concat "file://" (replace-regexp-in-string "tex$" "pdf" file)) t))

(defun evince-forwards-sync (file line col)
  (dbus-call-method 
   :session (get-evince-document file) "/org/gnome/evince/Window/0"
   "org.gnome.evince.Window" "SyncView"
   file (list :struct :int32 line :int32 col) 0))

(defun current-line-number ()
  (1+ (count-lines 1 (point))))

(defun do-evince-forwards-sync ()
  (interactive)
  (if (not (null (buffer-file-name)))
      (if (not (buffer-modified-p))
	  (if (string-equal (substring (buffer-file-name) -4)
			    ".tex")
	      (if (file-exists-p (replace-regexp-in-string 
				  "tex$" "pdf"
				  (buffer-file-name)))
		  (evince-forwards-sync (buffer-file-name)
					(current-line-number) 1)
		(message "You need to PDFLaTeX your file."))
	    (message "You can only forward sync LaTeX files."))
	(message "You need to save your buffer first"))
    (message "Forward sync only works in file buffers.")))

Además yo en particular puse

(global-set-key (kbd "< f1 >") 'do-evince-forwards-sync)

en mi .emacs, así que ahora si le pico F1 a mi compu mientras Emacs está en un archivo .tex que esté salvado, inmediatamente manda al PDF a la página correspondiente en Evince. De nuevo, GNOME 3 no permite que una aplicación le robe el foco a otra, así que Evince no se levanta, pero debería hacerlo en otros escritorios.

Está bastante padre cómo funciona el asunto, y además funciona (me parece) de forma suficientemente robusta. Ciertamente espero usarlo mucho durante los próximos artículos que escriba.

Imprimir entrada Imprimir entrada

Porque no tengo cosas importantes que hacer

Leyendo los comentarios de un blog, me encontré con esto. Es un estúpido jueguito parecido a Space Invaders, con una muy importante distinción: los “enemigos” son palabras, y para “matarlos” uno tiene que escribir dichas palabras.

Ztype

Ztype

En otras palabras, es un juego para ejercitar touch typing, el teclear rápidamente sin mirar el teclado. Después de cinco niveles tuve que forzarme a cerrar el tab de mi navegador, porque me percaté de que iba a perder todo el día ahí si no lo hacía. No sé cómo nunca se me había ocurrido un juego de este estilo: es de hecho útil, porque teclear sin mirar el teclado es de las primeras cosas que uno tiene que aprender para escribir rápido (ya sea prosa, \LaTeX, o código).

Si quieren tomarse un descanso de diez minutos mientras trabajan en la oficina, el jueguito no sólo es entretenidísimo, además sirve para practicar su touch typing.

Imprimir entrada Imprimir entrada

Mi Xperia Play es divertido, pero hay niveles

Platicando con Juanjo:

Yo: Ayer vi un PS3 nuevo, pero con la caja abierta, en 224 CAD. Pensé seriamente en comprarlo.
Juan: Pero el nuevo (con la caja cerrada) cuesta $250, ¿no?
Yo: Ajá. $249, de hecho. Eso es 10% de descuento.
Juan: Si es un lugar confiable, pues te ahorras esos $25, pero si no, yo preferiría comprarlo nuevo nuevo.
Yo: Pues es Best Buy.
Juan: Ah.
Yo: Como sea, 250 CAD (o USD) están de no manches.
Juan: La cosa es que ya tienes uno, ¿no?
Yo: La cosa es que está en México.

Imprimir entrada Imprimir entrada

Ah claro, uso Emacs

Cuando entré a mi posgrado, con esto de escribir notas, preámbulos extendidos y artículos, fue necesario que volviera a escribir documentos en \LaTeX regularmente, algo que había dejado de hacer durante mi carrera profesional, porque fuera de la academia \LaTeX es básicamente inútil. El PDF de mi novela fue generado por \LaTeX, pero la escribí en un archivito de texto con un formato que me inventé, y luego escribí un script en Perl que generaba un documento \LaTeX que luego compilaba a PDF. Y eso sólo porque soy un geek: la gente normal no usa \LaTeX, y la verdad no tendría razones para hacerlo.

Como sea, yo volví a escribir en \LaTeX, y para mi tesis de maestría decidí pasarme a la “modernidad” y escribirla en gedit. La verdad gedit es un editor más que pasable, y pude terminar mi tesis de maestría usando su plugin para \LaTeX, pero al final sí estaba sufriendo porque yo fui indoctrinado en el mejor editor que nunca jamás el mundo ha tenido, Emacs.

Sin embargo, después de utilizar Emacs al inicio de mi licenciatura, me pasé a XEmacs por una combinación de factores ligeramente idiotas. Entre ellos estaba el hecho de que leía mi correo electrónico dentro del editor, y eso funcionaba mejor en XEmacs que en Emacs, así que obviamente me pasé al primero. La verdad no lo lamento: XEmacs fue un gran editor durante muchos años, y yo lo usé alegremente como programador profesional, incluso durante las tristes ocasiones en que tuve que programar en Visual Studio (el editor de Visual Studio apesta).

Así fue hasta que llegó el momento de escribir mi tesis de maestría, cuando me pasé a gedit porque, entre otras cosas, XEmacs pasaba unos momentos dolorosos en ese entonces, donde el grupo de desarrolladores que lo mantenían no sabían qué hacer con múltiples tecnologías modernas que reemplazaban y/o mejoraban varios hacks que el venerable editor había tenido que implementar por su cuenta. Cosas como UTF-8, fuentes con antialias, y bibliotecas de interfaces gráficas, Emacs y XEmacs las habían tenido que implementar por su cuenta (¡y en Emacs Lisp, ‘arajo!), y cuando implementaciones más sensatas hicieron su aparición, los desarrolladores de ambos editores tardaron un rato en adaptarse a las nuevas tecnologías.

Así que escribí mi tesis de maestría en gedit, pero sí estaba sufriendo al final, extrañando todas las maravillas que Emacs y XEmacs siempre han ofrecido. Ya en el doctorado regresé a XEmacs, pero pronto descubrí que Emacs ahora jalaba mucho más bonito (se integra con GTK+ y tiene mejor soporte para fuentes con antialias), y regresé a usarlo después de casi diez años de tenerlo abandonado.

La cosa es que sencillamente ningún otro editor (con la posible excepción de Vim) le llega ni siquiera al ombligo (creo que sí ya superaron sus talones). El editor de GNOME, gedit, es endemoniadamente bueno, y con sus extensiones en Python se acerca (al menos potencialmente) al nivel de funcionalidad que Emacs ofrece al estar escrito, básicamente, en Emacs Lisp; sin embargo, la capacidad de navegar un documento rápidamente utilizando solamente el teclado es algo que está inherentemente alambrado en Emacs. Los algoritmos (y la implementación de los mismos) fueron escritos originalmente hace casi treinta años, y han sido refinados y optimizados durante todo ese tiempo. Es realmente ilusiorio esperar que el triste widget de texto de GTK+ (por bueno que sea) se le pueda acercar.

Llevo ya un par de años escribiendo \LaTeX en Emacs y siendo bastante rápido, pero había una cosa de las Mac que envidiaba. En las Mac, en TeXShop, uno está trabajando en un documento \LaTeX, y tiene el PDF compilado al lado, y si uno le pica a alguna parte del PDF y le dice que haga “sync”, el editor de texto se centra “cerca” del código \LaTeX que generó la parte del PDF clickeada. La tecnología es llamada SyncTeX, y de hecho Evince (mi visor de PDFs de siempre) la soporta desde hace un rato. Sin embargo, no creí que fuera posible utilizarlo en conjunción con Emacs, porque Evince (siendo parte de GNOME) no tiene una opción para configurar qué editor debe llamarse o de qué manera esto debe ocurrir para que enmarque la parte del documento deseada. A la gente de GNOME no le gusta “confundir” a los usuarios con opciones de configuración.

En su lugar, Evince tiene una señal en DBus que se dispara cuando el PDF visto fue compilado con SyncTeX, y uno hace Ctrl-click en una parte del mismo. Esa señal la captura quién sea que se conecte a DBus, y hace lo que sea que tenga que hacerse con la información que pasa la señal (básicamente el nombre del archivo .tex, y la línea correspondiente). Entonces yo creí que no había forma de que yo pudiera hacer eso desde Emacs, porque DBus es una tecnología “moderna”.

Y por supuesto resultó que estaba equivocado porque, claro, uso Emacs.

Por supuesto algún demente implementó soporte para DBus en Emacs, y entonces fue sólo cosa de poner lo siguiente en mi .emacs:

;; SyncTex from DBus
(require 'dbus)

(defun synctex-find-file (file)
  (find-file (substring file 7))
  (goto-line (car linecol))
  (unless (= col -1)
    (move-to-column col)))

(defun synctex-switch-to-buffer (file)
  (switch-to-buffer buf)
  (goto-line (car linecol))
  (unless (= col -1)
    (move-to-column col)))

(defun my-evince-sync (file linecol time)
  (let ((buf (get-file-buffer (substring file 7)))
        (line (car linecol))
        (col (cadr linecol)))
    (if (null buf)
      (synctex-find-file file)
      (synctex-switch-to-buffer buf))))

(dbus-register-signal
 :session nil "/org/gnome/evince/Window/0"
 "org.gnome.evince.Window" "SyncSource"
 'my-evince-sync)

El código original lo saqué de aquí, y lo modifiqué para que funcionara con Evince 3 (porque yo uso GNOME 3), y para que si no estaba abierto el buffer del archivo Emacs lo abra. Con eso, y teniendo la ventana de Evince “Always on top”, funciona casi exactamente igual que en un Mac:

Emacs con SyncTeX

Emacs con SyncTeX

De hecho funciona mejor; hasta ahora, el editor se centra exactmente en la línea correcta, no como en TeXShop.

Esto me hizo reevaluar mi opinión acerca de que Evince no tenga una opción para especificar qué editor debe “actualizarse” cuando uno hace Ctrl-click: la idea de que el visor sólo dispare una señal, y que sólo los editores conectados a DBus respondan como debe de ser me parece fabulosamente elegante, y ciertamente mucho más flexible y poderosa. Tal vez la gente de GNOME de hecho tenga la razón con esta idea de no ofrecer miles opciones de configuración a lo idiota (el plugin de gedit que aprovecha SyncTeX funciona igual: sólo se conecta al bus y captura la señal correspondiente de Evince).

Fue divertido que mi fe flaqueara un poco, ¿cómo pude dudar que hubiera algo que Emacs no puede hacer? Si como todo mundo sabe, Emacs es un buen Sistema Operativo; lástima que le falte un buen editor.

Real Programmers

Real Programmers
Imprimir entrada Imprimir entrada