Xenobots y la emergencia

Hace cosa de un mes la Universidad de Vermont publicó una investigación sobre los Xenobots, unos "robots vivientes" capaces de desplazarse en una dirección y autorepararse si son dañados.

Estos organismos están compuestos de dos piezas distintas: células del miocardio —el tejido muscular del corazón— que se contraen periódicamente de forma involuntaria y células de la piel con una función puramente estructural. Ambas fueron obtenidas a partir de embriones de ranas.

Un grupo de investigadores informáticos de esta universidad usaron un supercomputador para encontrar las configuraciones capaces de desplazarse distancias más largas en un periodo de 10 segundos. Cada una de las configuraciones era un mosaico tridimensional de estas células, cuyo resultado ya no es una rana.

Figura sobre el ensamblado de Xenobots

De momento es una primera prueba lejos de la manufactura a gran escala, y las configuraciones más efectivas fueron replicadas a mano por un cirujano con pinzas y electrodos en miniatura. Sin embargo, los autores imaginan que conforme avance la bioimpresión podrían tener muchas aplicaciones.

En el futuro se podrían usar criaturas de este tipo para limpiar plástico de los océanos, eliminar contaminación radioactiva o llevar medicamentos a partes específicas del cuerpo. Pero sobre todo es un primer paso hacia entender su comportamiento colectivo.

Aunque han sido entrenados con el objetivo de moverse hacia delante, esto ha permitido modelar su comportamiento individual. Sin embargo, cuando muchas de estas criaturas interaccionan entre sí, emergen comportamientos que no se pueden predecir con facilidad, como dar vueltas en círculo.

Este fenómeno que se conoce como emergencia ha suscritado la inquietud de la comunidad científica. Los comportamientos emergentes, tan difíciles de predecir, son un tema muy recurrente en la ciencia ficción.

MicroBenders autoreplicados (Futurama 6x17)

Existe un escenario de catástrofe global muy popular que se conoce gray goo. En este supuesto, el desarrollo de cierta tecnología capaz de autoreplicarse se descontrola hasta agotar todos los recursos del planeta.

En este caso no tenemos por qué preocuparnos, ya que la forma en que se han diseñado los xenobots los priva de muchas funciones necesarias para mantenerse vivos más de una semana. O al menos eso parece.

No hay comentarios

Diseño paramétrico con OpenSCAD

Cada vez que diseño algo con un editor 3D como Blender, mi programador interior me susurra una y otra vez: "si luego tienes que modificar eso que estás haciendo, ajustarlo todo va a ser un infierno".

Y no le falta razón. Si no estamos diseñando algo puramente artístico, un error al principio del diseño implica mucho esfuerzo de corrección más adelante y muchos clicks de ratón. Es por eso que existen lenguajes de programación especializados en diseño paramétrico como OpenSCAD.

Render 3D de los coronadados

Este set de dados del coronavirus que diseñé hace poco está compuesto de 81,454 vértices, pero su código fuente consiste en apenas 140 líneas. Y si quisiera modificar algún parámetro como el tamaño de las patas o el texto de alguna de sus caras sólo tendría que cambiar el valor de una variable.

Los lenguajes paramétricos no son una novedad y casi todos los editores 3D permiten programar modelos de una u otra forma. Lo que hace a OpenSCAD especial es la simpleza de su sintaxis y la velocidad con la que se puede diseñar cualquier pieza.

Este lenguaje consiste en unos pocos comandos básicos para crear volúmenes y aplicarles operaciones. De hecho, la especificación completa de OpenSCAD cabe en este cheatsheet tan apañado con enlaces a ejemplos de uso de cada función en la wiki oficial.

Como demostración de su potencia, voy a explicar cómo diseñar un modelo sencillo paso a paso. Si quieres ir probando a programarlo al mismo tiempo puedes instalar OpenSCAD, que ocupa unos 20MB. El modelo que he escogido es una pera, que aunque no tiene mucha utilidad es bastante ilustrativa.

Empezaremos por crear una esfera de 20mm de radio. Sería suficiente con llamar a sphere(20), pero es mejor guardar todas las medidas en variables al principio del código para hacer más cómodo el ajuste. Todas las líneas deben acabar en ; o se considerarán parte de la siguiente línea.

r = 20;

sphere(r);
Primer paso: una esfera simple.

Al pulsar F5 se renderizará la esfera que aparece a la derecha. Ahora vamos a crear una segunda esfera más pequeña a la que le aplicaremos una transformación de tipo translate([x, y, z]) para elevarla un poco. Las transformaciones se aplican al siguiente objeto de una misma línea. En este caso, al no haber ;, se aplicará a la esfera de la línea siguiente.

r = 20;

sphere(r);

translate([0, 0, r*1.5])
sphere(r/4);
Segundo paso: Una segunda esfera pequeña más arriba.

Así podemos concatenar varias transformaciones a un mismo objeto que se pueden leer como "traslada a esta posición la esfera con este radio". Además de las transformaciones, también podemos realizar operaciones sobre más de un objeto al mismo tiempo, como es el caso de hull, que aplicaremos para obtener la envolvente de nuestras dos esferas.

r = 20;

hull() {
    sphere(r);

    translate([0, 0, r * 1.5])
    sphere(r / 4);
}
Tercer paso: operación hull para dar forma de pera.

Al usar las llaves hemos aplicado la operación hull a un conjunto de cuerpos, dando como resultado otro objeto al que también podemos aplicar transformaciones. En este caso le aplicaremos la transformación color para darle color de pera. Y ya de paso vamos a modificar la variable especial $fn —fragments number— para aumentar la calidad del renderizado.

$fn = 64;
r = 20;

color("#de3") hull() {
    sphere(r);
    translate([0, 0, r * 1.5])
    sphere(r / 4);
}
Cuarto paso: Más resolución y color.

Esta forma de programar volúmenes va moldeando nuestra forma de pensar en el diseño. Al reducirlo todo a formas básicas e ir añadiendo operaciones y transformaciones poco a poco, vamos construyendo nuestros modelos de forma incremental. El siguiente incremento será usar la operación de difference para restarle una esfera a toda la pera, que haga las veces de mordisco.

$fn = 64;
r = 20;
difference() {
    // color("#de3") hull()...
    color("#cea")
    translate([r * 3/4, 0, r / 2])
    sphere(r * 3/4);
}
Quinto paso: Diferencia con otra esfera para hacer un bocado.

Ya sólo nos falta el rabillo de la pera. Podemos definir unas cuantas variables con las que crear un cilindro, inclinarlo un poco, trasladarlo a la punta de la pera y darle un color marrón. Como no hemos usado cylinder hasta ahora, podemos echar un vistazo a su sintaxis en el cheatsheet.

sa = 10;
sr = 2;
sh = 10;

color("#632")
translate([0, 0, r * 7/4])
rotate([sa, sa, 0])
cylinder(r=sr, h=sh);
Sexto paso: rabillo de la pera.

Y ya podríamos dar esta pera por terminada. Sin embargo, las variables que hemos usado no tienen unos nombres demasiado significativos. OpenSCAD nos permite usar comentarios para darles un nombre y un rango de valores válido sin hacer nuestro código más complejo.

// Model quality (fragments number)
$fn = 64;  // [32,64,128,256]
// Pear radius
r = 20;  // [1:.1:100]
// Stalk angle
sa = 10;  // [0:360]
// Stalk radius
sr = 2;  // [1:.1:10]
// Stalk height
sh = 10;  // [1:20]

Al usar esta sintaxis, el propio software nos generará un panel de ajustes que sólo nos dejará modificar las variables en los rangos definidos. Esto también es útil para decirle a otros usuarios cuáles son los rangos más seguros en los que ajustar un parámetro sin destrozar el modelo.

Parámetros customizables

He subidoa la web el código completo del ejemplo. Tiene muchos números mágicos por mantenerlo sencillo, aunque en un diseño real es aconsejable no usar ningún número que no esté definido en una variable, para hacer el modelo lo más paramétrico y fácil de ajustar posible.

Si quieres ver algunos ejemplos reales, puedes echar un vistazo a algunas de mis creaciones, como estos especieros minimalistas o esta base con césped para poner macetas.

No hay comentarios

Radios del mundo

Radio Garden es una especie de Google Earth de emisoras de radio con la que puedes explorar los sonidos en directo de otras partes del planeta.

Vista de radio.garden centrada en Europa

Este proyecto empezó en 2016 como una prueba conceptual para un proyecto de investigación holandés con una idea sencilla: tomar los streams online de radios AM/FM de todo el mundo y colocarlos en un globo terráqueo.

Como cualquiera puede añadir una emisora, el mapa ha crecido rápidamente. Aunque sigue habiendo mucha más densidad de emisoras en Europa, se pueden encontrar algunas en los lugares más remotos del planeta.

Es muy visual e invita a explorar los sonidos del mundo de la misma forma que Street View invita a perderse por las calles de otros países. Hay una lista de emisoras favoritas a la que añadir las que vayamos encontrando.

Con el tiempo he ido añadiendo a esta lista algunas radios musicales que suelo escuchar, aunque también hay muchas radios convencionales que pueden estar bien para practicar un idioma.

Si encontráis alguna otra radio interesante, podéis dejarla en los comentarios. Sigo buscando alguna emisora de lofi que reemplace a ese vídeo de YouTube con las mismas 8 horas de música en bucle.

No hay comentarios

El problema del User Agent

Hace casi 25 años, un popular navegador web llamado Mosaic tuvo una idea: incluir una cabecera en sus peticiones con la versión del navegador. Así, cada vez que un servidor recibiera una petición, podría saber con exactitud qué versión del navegador se estaba usando al otro lado.

User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

Al principio no se le dio mucho uso. Sin embargo, otro navegador llamado Netscape la popularizó poco después. Como las funcionalidades de ambos navegadores eran distintas, algunos servidores empezaron a usar esta cabecera para determinar qué respuesta enviar a los usuarios.

User-Agent: Mozilla/1.0N (Windows)

En vez de usar su propio nombre, este predecesor de Firefox usó el nombre de su mascota: Mozilla, abreviatura de Mosaic-killer. Pero esto sólo era el principio de la confusión. Otros navegadores como Internet Explorer usaron el nombre de Mozilla para indicar que eran compatibles con él.

User-Agent: Mozilla/2.0 (compatible; MSIE 3.02; Windows 95)

Y esto sólo fue a más. Algunos navegadores como Opera le dieron al usuario la opción de elegir a qué navegador querían suplantar mediante un menú desplegable. Los servidores estaban cada vez más confusos y saber quién había al otro lado se estaba volviendo muy difícil.

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en)
User-Agent: Mozilla/5.0 (Windows NT 6.0; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0
User-Agent: Opera/9.51 (Windows NT 5.1; U; en)

Actualmente, los navegadores utilizan un User-Agent que poco tiene que ver con su nombre. Algunos servidores usan gigantescas bases de datos para detectar el navegador actual a partir de esta cabecera y así servir la versión de la página más compatible. Por ejemplo, tu User-Agent es:

Desconocido

Es por esto que las herramientas modernas para asegurar la compatibilidad como Modernizr o Polyfill.io ya no confían en el User-Agent. En su lugar, hacen pequeñas pruebas en el propio navegador para saber qué es capaz de hacer. Esta técnica se conoce como feature detection.

La existencia de millones de User-Agent distintos también ha facilitado el browser fingerprinting: un conjunto de métodos que usan esta cabecera y otros datos de tu navegador para identificarte de forma única pese a que te conectes tras un proxy, en incógnito y con un bigote postizo.

Aunque esta cabecera nació con un buen propósito, el paso del tiempo la ha convertido en algo distinto. Por eso, Chrome ha anunciado que va a dejar de usarla y otros navegadores como Firefox han apoyado esta decisión. Pero ¿qué implica actualmente dejar de usarla por completo?

Hace una semana me instalé un plugin para Chrome llamado User-Agent Switcher and Manager y dejé esta cabecera vacía, para ver cómo respondían las webs que suelo visitar. Os dejo algunos de los resultados que resumen esta experiencia:

Como veis, aun no estamos preparados para un cambio en esta dirección, y seguramente muchas webs nunca lleguen a estarlo. Por suerte Chrome va a aplicar este cambio de forma gradual, empezando por no volver a actualizar el valor de esta cabecera. Adiós, User Agent!

No hay comentarios

Tuenti Challenge 10: nivel 18

Hemos construido un nuevo servicio, y debido a la falta de coordinación entre dos equipos, cada uno necesita recibir ciertos datos en un formato distinto: Elements Separated by Commas (ESC) y List-Oriented Language with Multiple Advanced Options (LOLMAO).

Matters with Formatters, fue el último del Tuenti Challenge 10 al que llegué. Como la solución optimizada para el caso de submit se me quedó en el tintero, me propuse terminarla y aquí va el resultado. Puedes encontrar en mi repositorio de Github del concurso mi solución completa.

Dada una cadena válida en uno de los dos formatos, debemos calcular el número mínimo de cambios necesarios para hacerla válida en el otro. No nos importa lo que represente esta cadena, lo único que queremos es que la cadena resultante sea válida en ambos formatos.

Para que una cadena sea válida en formato ESC nos basta con que cada línea del texto contenga al menos dos elementos separados por una coma.

alfa,beta,gamma
,,
foo,bar
test,a[5],asdf

El formato LOLMAO es algo más restrictivo:

[[],foo,[[[,],[1,2],5,,],some0literal
with3multiple
lines]]

La cadena de entrada del problema puede ser un texto de hasta 400 caracteres en formato LOLMAO que debemos convertir a ESC, o un texto de hasta 4000 caracteres en formato ESC que debemos convertir a LOLMAO.

En caso de que no sea posible generar una cadena válida en ambos, debemos devolver IMPOSSIBLE. Viendo estas reglas es fácil deducir que toda cadena en ESC debe contener al menos una lista de dos elementos, y por tanto una coma. Para que una cadena con una coma sea válida en LOLMAO, deberá estar rodeada de corchetes. Así, la cadena más pequeña que es válida en ambos formatos es [,] y cualquier cadena menor será imposible.

Ejemplo de exploración de solución

El resto de casos debemos resolverlos explorando cambios en la cadena de entrada. Nos enfrentamos a un problema de programación dinámica, en el que partiendo de un estado inicial —la cadena de entrada— debemos alcanzar un estado final —la cadena válida— en el mínimo número de pasos.

A fin de simplificar las cadenas de entrada, voy a reemplazar cualquier símbolo de la siguiente forma:

def standarize(text):
    res = ''
    for char in text:
        if char in '[],':
            res += char
        elif char == '\n':
            res += 'N'
        else:
            res += 'O'
    return res

A continuación, podríamos representar los estados como cadenas modificadas pero esto permitiría que pudiéramos explorar estados inválidos. Una de las máximas para optimizar la exploración es escoger una representación de estados que permita representar el mínimo número de estados inválidos.

Si nos peleamos un rato con este problema, veremos que cualquier estado se puede reducir a cuatro valores:

  1. La posición en la cadena: inicialmente será cero, y cada modificación consistirá en alterar el valor del símbolo en esta posición y generar un nuevo estado cuya posición se incremente en uno. Un estado final será aquel cuya posición sea igual a la longitud de la cadena.
  2. La profundidad: como el formato LOLMAO permite anidar listas, cada vez que encontremos un símbolo [ en la cadena la profundidad aumentará en uno, y cada vez que encontremos un símbolo ] la profundidad se reducirá en uno. Inicialmente, la profundidad es cero.
  3. El último símbolo encontrado: inicialmente no estará definido, y vez que pasemos al siguiente estado, tendrá el valor del último símbolo que hemos usado. Nos será útil para evitar combinaciones inválidas como ]O, N[ o ][.
  4. Si ha habido una coma en esta línea: este valor comenzará siendo falso, siempre que lleguemos a una coma será verdadero, y cuando alcancemos una nueva línea volverá a ser falso. Nos servirá para cumplir la restricción de ESC de tener una coma por línea.

Como esta representación está muy reducida, algunas cadenas distintas se pueden representar mediante un mismo estado. Esto quiere decir que si guardamos en memoria los resultados de nuestra exploración, se reutilizarán con bastante frecuencia.

@lru_cache(None)
def changes(pos, depth, last_char, comma_since_newline):
    # TODO compute here the alternatives "alts" as a list of symbols
    res = min(
        (alt != _text[pos]) + changes(
            pos + 1,
            depth + (alt == '[') - (alt == ']'),
            alt,
            alt != 'N' and comma_since_newline or alt == ','
        )
        for alt in alts
    )
    return res

Mientras resolvía este problema descubrí ese decorador @lru_cache(size), que se puede usar en cualquier función para almacenar en memoria su resultado, de forma que la próxima vez que se llame a esa función con los mismos argumentos, su resultado no sea calculado de nuevo.

Ya sólo nos queda saber cuándo debemos generar como alternativa cada uno de los cinco símbolos posibles:

Con esta aproximación conseguimos resolver el caso de test con facilidad. Pero al llegar al caso de submit la cosa se complica. Algunas cadenas son excesivamente largas y no logramos resolverlas mediante este método. Pero aun nos queda un as bajo la manga.

Como sabemos que las cadenas que deben ser convertidas a LOLMAO tienen como máximo 400 caracteres, cualquier cadena más larga que nos llegue deberá ser convertida a formato ESC. Si encontramos una forma rápida de hacer esta conversión sin romper el formato LOLMAO no tendremos que lanzar el algoritmo de exploración.

En este caso, el truco es el siguiente: en primer lugar, cambiar si fuera necesario el primer símbolo por [ y el último por ]. A continuación, cada vez que lleguemos a un salto de línea que no ha tenido una coma en esa línea, reemplazaremos ese salto por una coma.

De esta forma lograremos reducir el problema rápidamente y todos los casos se resolverán sin problema. El único cambio que tuve que hacer fue aumentar la profundidad máxima de la pila de llamadas debido a mi implementación recursiva de la solución.

setrecursionlimit(10000)  # we need to go deeper

Y aquí acaba la serie sobre el Tuenti Challenge 10. Todavía quedan dos niveles que no llegué a resolver, aunque igual algún día me pongo a resolverlos. Puedes consultar además los niveles anteriores:

No hay comentarios

🍪 ¿Cookies?

Esta web usa cookies para identificar qué contenido es interesante y escribir más contenido similar. Puedes obtener más información aquí.