Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas
Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas

sábado, 18 de marzo de 2017

Más conjuntos de Mandelbrot


En la anterior entrada vimos por encima qué era el conjunto de Mandelbrot y cómo se podía generar el gráfico. Recordamos que todo se basaba en la función generadora z2+c. Se puede cambiar la función y repetir el proceso con otras. Estudiemos otras variantes:

  • (z+c)(z-c)
  • z3+c
  • z4+c

Para implementar estas nuevas funciones, dado que hay que hacer algunas operaciones un tanto tediosas, pensé en utilizar alguna librería Javascript que facilitase el trabajo con números complejos. Probé Mathjs, pero es demasiado lenta, así que se ha quedado todo como Javascript plano.


De nuevo, obtenemos unos bonitos fractales.



(z+c)(z-c)


z3+ c



z4+c






En esta página tenemos el código modificado:

jueves, 16 de marzo de 2017

El conjunto de Mandelbrot para 'dummies'




¿Quién no se ha sentido atraído por la belleza de esta figura? La representación gráfica del conjunto de Mandelbrot es el típico ejemplo de figura fractal y es, probablemente, la más popular.


Pero, ¿qué es exactamente, y cómo se llega a esta figura?

Definiendo el conjunto de Mandelbrot


Pertenecen al conjunto de Mandelbrot todos aquellos números complejos que cumplen una propiedad: que el valor de una sucesión determinada, que ahora veremos, para un determinado número, es convergente, es decir, no tiende a infinito.

La sucesión de la que hablamos es recursiva, y  es esta:

zn+1 = z2 + C

donde C es el número que estamos tratando de comprobar si pertenece al conjunto o no y z se va calculando recursivamente, esto es, tomando como entrada de cada iteración el valor obtenido anteriormente.

Recordemos que la aritmética de números complejos involucra operaciones con binomios del tipo a+bi, donde i es la unidad compleja. Recordemos que las operaciones en las que interviene i son un poco especiales, en este caso debemos recordar que i2 es -1.

Vamos a hacer unas cuentas con la ayuda del intérprete de Python, que tiene soporte de serie para números complejos (la unidad imaginaria se denota con j en Python).

Primero, definimos la función que aplicaremos de forma recursiva:
>>> def fm(z, c):
...     return z**2 + c

Ahora, vamos a probar qué pasa con algunos números al aplicarle la función. Empezaremos con el número 1 + i, en notación Python habría que escribirlo como 1 + 1j. Aplicaremos la función anterior cinco veces seguidas, en la primera iteración tanto z como c serán 1 + i y en las siguientes, el resultado de la función se usa como entrada para la siguiente. Es más difícil decirlo que hacerlo:
>>> z = c = 1+1j
>>> for x in range(5):
...     z = fm(z, c)
...     print z
... 
(1+3j)
(-7+7j)
(1-97j)
(-9407-193j)
(88454401+3631103j)

Vemos que tras apenas 5 iteraciones, los valores se van disparando, así que, claramente, la sucesión de valores es divergente, se va a infinito.

Probemos con 0.5 + 0.3j:
>>> z = c = 0.5 + 0.3j
>>> for x in range(5):
...     z = fm(z, c)
...     print z
... 
(0.66+0.6j)
(0.5756+1.092j)
(-0.36114864+1.5571104j)
(-1.79416445761-0.82469660658j)
(3.03890160806+3.25928267968j)

También parece que diverge, pero no tan rápido como el caso anterior.

Otro caso más, con el número 0.2 - 0.1j:


>>> z = c = 0.2 - 0.1j
>>> for x in range(5):
...     z = fm(z, c)
...     print z
... 
(0.23-0.14j)
(0.2333-0.1644j)
(0.22740153-0.17670904j)
(0.220485371029-0.180367812122j)
(0.216081251188-0.179536927955j)

Tras cinco iteraciones, no parece que se vaya a infinito. Probemos otras cinco iteraciones:


>>> for x in range(10):
...     z = fm(z, c)
...     print z
... 
(0.214457598616-0.177589128054j)
(0.214454163201-0.176170675885j)
(0.214954481072-0.175561069755j)
(0.21538373972-0.175475277291j)
(0.215598582395-0.175589042903j)
(0.215651236743-0.175713497468j)
(0.215630222717-0.175785666083j)
(0.215595792549-0.175809404656j)
(0.215572598999-0.175807535868j)
(0.215563255771-0.175798574862j)

 Parece claro que este número, 0.2 - 0.1j, al aplicarle la función del principio, la sucesión que se obtiene es convergente.

Se dice que un número pertenece al conjunto de Mandelbrot cuando la sucesión es convergente, es decir, no se va a infinito. Se puede probar que en cuanto que uno de los resultado de la sucesión se sale del rango de ± 2 ± 2i, la sucesión será divergente, esto es, el número no pertenecería al conjunto. La forma más rápida de calcular esto es comprobando, para cada uno de los resultados intermedios, si el módulo del número complejo es > 2, o lo que es lo mismo, si la suma de los cuadrados de sus partes real e imaginaria es > 4. De esta última forma llegamos al mismo resultado ahorrándonos hacer una raíz cuadrada, que siempre es más costosa computacionalmente.

Sabiendo esto, vamos a empezar a comprobar todos los posibles números imaginarios que hay entre -2 - 2i y 2 + 2i, y viendo si la sucesión que se deriva de cada uno de ellos diverge. Obviamente, hay infinitos números en esta región, así que tenemos que ir calculando el mayor número de ellos con pequeños incrementos.

De la misma forma, tendremos que hacer unas cuantas iteraciones para saber si la sucesión diverge o no. Por ejemplo, para el número 0.5 + 0.3j que vimos antes tuvimos que hacer 5 iteraciones o calcular 5 términos de la sucesión antes de darnos cuenta de que realmente divergía.

Empecemos de forma sencilla: moviéndonos en pasitos de 0.1 y haciendo 3 iteraciones. Tendríamos esta representación gráfica:



Vemos que tenemos muy poco nivel de detalle, incrementemos paulatinamente el nivel de detalle (haciendo los incrementos más pequeños y aumentando el número de iteraciones para "cazar" más números que están fuera del conjunto), ahí van unas cuantas muestras:





En este punto, ya tenemos un detalle bastante fino, pero es probable que, como hemos hecho pocas iteraciones, haya números que pensemos que pertenecen al conjunto, pero en realidad, no pertenecen. Aumentemos las iteraciones.





Hasta ahora nos hemos limitado a colorear en rojo los números que, al aplicar la función, la sucesión diverge; en negro, los que converge.

El siguiente paso en detalle, y aquí es donde aparece la "magia" de este conjunto, es asignar diferentes colores a la velocidad con la que los números que no pertenecen al conjunto, al evaluar la sucesión, divergen. Esto es, si asignamos colores diferentes al número de iteraciones necesarias para determinar si el número no pertenece al conjunto, empezamos a obtener unos bonitos gráficos.

La gracia en este caso es encontrar una gradación de colores y asignación al número de iteraciones que quede bonita. Por ejemplo, una sencilla, basada en amarillos:



Un último ejemplo, con mayor resolución, utilizando una gama más o menos aleatoria de colores:


Implementación


Es bastante sencillo implementar una representación gráfica del fractal. Esta que se muestra está hecha en HTML Canvas y Javascript, usando "plain Javascript", sin plugins ni librerías.

Lo primero que debemos hacer es definir un canvas en nuestro HTML. También hemos agregado unos input para poder jugar con el incremento y el número de iteraciones a probar.


<canvas id="canvas"></canvas>
<form>
<p>Increment: <input name="increment" value="0.01"></p>
<p>Iterations: <input name="iterations" value="1000"></p>
<p><input type="button" value="Run!" id="run"></p>
<p id="message"></p>
</form>

A continuación, escribimos el código Javascript que hará los cálculos y representará los puntos en pantalla. El primer paso, es acceder e inicializar el elemento canvas:


var width = 600;
var height = 600;
var c = document.getElementById("canvas");
c.setAttribute('width', width);
c.setAttribute('height', height);
var ctx = c.getContext("2d");

Ahora definimos algunas constantes y funciones:
  • adjust_scale(i, j) => centra los puntos correctamente en el gráfico. Nuestro sistema de coordenadas es el mátemático (0, 0) en el centro, en un canvas el (0, 0) es la esquina superior izquierda.
  • get_color(nIter) => devuelve un color en función del número de iteraciones. En esta función se puede ser creativo y experimentar para probar diferentes gamas de colores y gradaciones. En nuestro ejemplo nos limitamos a una gama de rojos.
  • mandelbrot(z, c) => calcular un término de la sucesión aplicando la fórmula zn+1 = z2 + C, ya explicada.
  • in_mandelbrot_set(c, maxIter) => determina si un número C está en el conjunto de Mandelbrot utilizando un máximo de iteraciones. Devuelve un objeto con dos claves: si la sucesión es convergente y el número de iteraciones necesarias para llegar a dicha conclusión.
  • run() => lanza el gráfico.

El código completo es este:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<canvas id="canvas"></canvas>
<form>
<p>Increment: <input name="increment" value="0.01"></p>
<p>Iterations: <input name="iterations" value="1000"></p>
<p><input type="button" value="Run!" id="run"></p>
<p id="message"></p>
</form>

<script>
var width = 600;
var height = 600;
var c = document.getElementById("canvas");
c.setAttribute('width', width);
c.setAttribute('height', height);
var ctx = c.getContext("2d");

var mandelbrot_limit = 1.8;
var mandelbrot_max_module = 4;



function adjust_scale(i, j) {
    return [(width/(2*mandelbrot_limit))*i + (width/2), (height/(2*mandelbrot_limit))*j + (height/2)];
}

function get_color(nIter) {
    var factor = 50 + nIter;
    factor = parseInt(factor);
    if (factor > 255) factor = 255;
    return "rgb("+factor+",0,0)";
}

function mandelbrot(z, c) {
    var x = z[0]; 
    var y = z[1];
    var real = x*x - y*y + c[0];
    var imag = 2*x*y + c[1];
    return [real, imag];
}

function in_mandelbrot_set(c, maxIter) {
    var x = 0;
    var res = c
    while (x < maxIter) {
        res = mandelbrot(res, c);
        if ((res[0]*res[0] + res[1]*res[1]) > mandelbrot_max_module) {
            return {'status': false, 'nIter': x}; // No está en el conjunto, hemos tardado x interaciones en saberlo
        }
        x++;
    }
    return {'status': true, 'nIter': x};
}

function run() {
    ctx.fillStyle = "#FFFFFF";
    ctx.fillRect(0, 1, width, width);

    var mandelbrot_inc = parseFloat(document.forms[0].elements.increment.value);
    var mandelbrot_test_iterations = parseInt(document.forms[0].elements.iterations.value);
    var point_width = 1;
    if (mandelbrot_inc >= 0.0009) point_width = 2;
    if (mandelbrot_inc >= 0.009) point_width = 8;       
    if (mandelbrot_inc >= 0.09) point_width = 16;       

    document.getElementById("message").innerHTML = "Increment: "+mandelbrot_inc+", Iterations: "+mandelbrot_test_iterations;
    for(var i=-mandelbrot_limit; i<=mandelbrot_limit; i += mandelbrot_inc) {
        for (var j=-mandelbrot_limit; j<=mandelbrot_limit; j += mandelbrot_inc) {
            check = in_mandelbrot_set([i, j], mandelbrot_test_iterations);
            if (check.status) {
                ctx.fillStyle = "#000000";
            } else {
                ctx.fillStyle = get_color(check.nIter);
            }
            var point = adjust_scale(i, j);
            ctx.fillRect(point[0], point[1], point_width, point_width);
        }
    }
}

document.getElementById("run").addEventListener('click', run);

</script>

martes, 11 de octubre de 2016

Euler #5, #6 y #7

https://projecteuler.net/problem=5
   1 #!/usr/bin/env python3
2
3 divisors = range(1, 20);
4 test = divisors[-1]
5 control = True
6 while control:
7 divisible = True
8 for d in divisors:
9 if test % d != 0:
10 divisible = False
11 break
12
13 if divisible:
14 control = False
15 else:
16 test += 1
17
18 print (test)
19





https://projecteuler.net/problem=6

JavaScript:
   1 
2 var sum = 0;
3 var squares = 0;
4 for (var i = 0; i <= 100; i++) {
5 sum += i;
6 squares += i*i;
7 }
8 console.log(sum * sum - squares);
9





https://projecteuler.net/problem=7

PHP:
   1 <?php
2 // https://projecteuler.net/problem=7
3
4 function isPrime($num) {
5 if ($num == 2) return true;
6 if ($num < 2 || $num % 2 == 0) return false;
7 $max = (int) sqrt($num);
8 for ($i = 3; $i <= $max; $i +=2) {
9 if ($num % $i == 0) {
10 return false;
11 }
12 }
13 return true;
14 }
15
16 $counter = 0;
17 $num = 2;
18 $stop = false;
19 while (true) {
20 if (isPrime($num)) {
21 $counter++;
22 if ($counter == 10001) {
23 echo "$num\n";
24 break;
25 }
26 }
27 $num++;
28 }
29

domingo, 9 de octubre de 2016

Euler #1 y #2

Hay que mantenerse en forma...

https://projecteuler.net/problem=1

Python:
   1 #!/usr/bin/env python3
2
3 sum = 0
4 for n in range(1, 1000):
5 if n % 3 == 0 or n % 5 == 0:
6 sum += n
7
8 print (sum)
9




JavaScript:
   1 var sum = 0;
2 for (var i=1; i<1000; i++) {
3 if (i % 3 == 0 || i % 5 == 0) {
4 sum += i;
5 }
6 }
7 console.log(sum)
8





https://projecteuler.net/problem=2

PHP (iterativo):
   1 <?php
2
3 function fib($max) {
4 $ret = array(1, 2);
5 $x = 3;
6 while (true) {
7 $new = $ret[$x-2] + $ret[$x-3];
8 if ($new <= $max) {
9 $ret []= $new;
10 $x++;
11 } else {
12 break;
13 }
14 }
15 return $ret;
16 }
17
18
19 $sum = 0;
20 foreach (fib(4000000) as $n) {
21 if ($n % 2 == 0) {
22 $sum += $n;
23 }
24 }
25
26 echo "$sum\n";
27


 

jueves, 12 de enero de 2012

Desmitificando HTML5

Cliente: [...] y quiero que la web corporativa se haga en HTML5.
Desarrollador: ¿Necesita algún elemento nuevo de HTML5 en concreto?
Cliente: Uh... Esto... Bueno, quiero que tenga vídeo, gráficas interactivas, todo lo que trae HTML5.
Desarrollador: Sin problema, pero el precio del desarrollo subirá. Los gráficos en HTML5 hay que programarlos igualmente, lo único que en vez de Flash utilizaremos Canvas. Si queremos que el vídeo se vea en todos los navegadores, habrá que hacer diferentes codificaciones: necesitaremos más almacenamiento y hardware más potente para la codificación si hay mucho volumen de vídeos.
Cliente:¿Pero eso no viene ya de serie con el HTML5 ése?
Desarrollador: No, hay que hacerlo igualmente.
Reconozco que esta esta conversación ficticia está un poco traída por los pelos, pretende ser más satírica que otra cosa, pero ilustra un poco la sensación que tengo respecto al HTML5: parece que es la panacea y la solución a todo, la tecnología que finalmente hará de la web un lugar idílico, normalizado y estandarizado,... pero sigue haciendo falta alguien que programe.

Hay muchas novedades y aportaciones en HTML5 que ya han analizado en multitud de sitios, así que no voy a decir nada nuevo. Lo que quiero transmitir con este artículo es que HTML5 no es un milagro tecnológico ni nada similar (se oye demasiado a menudo "¡... y está hecho con HTML5...!", como si fuese lo más de lo más de la innovación y la solución a todos los problemas). Es una evolución (con muchos aciertos y posiblemente, limitaciones) del HTML y así es como los técnicos debemos verla. No nos dejemos llevar por el "hype" de las siglas y de los departamentos de Marketing ;-)

Algunas cosas que me parecen interesantes (en la Wikipedia viene un buen resumen):
  • Mejoras semánticas (en mi opinión, casi la novedad más importante). En versiones anteriores, cada bloque en una página no tenía sentido semántico. Es decir, la cabecera, menú, pie de página, etc... eran siempre un
    . Con HTML5 tenemos , , , ...
    Esto parece una tontería, pero es muy importante y tiene muchas aplicaciones:
    • Los buscadores pueden identificar rápidamente qué partes de la página son relevantes.
    • Los agentes de usuario (navegadores) pueden presentar la página de acuerdo a las instrucciones que le de el usuario, por ejemplo: "ocultar la barra de menús" o "destacar el contenido del artículo", etc.
      Imaginemos un navegador/lector para personas con problemas de visión: cada vez que pasen de página no les leerá el interminable menú de navegación, ni la cabecera, etc. Directamente les leerá el contenido del artículo.
    • Se facilita enormemente el intercambio de contenidos entre sitios web (y el "robo" también, todo hay que decirlo). Nuestros scripts ya sabrán qué hay que recoger en una página y qué no.
  • Por fin se puede mostrar vídeo en el navegador sin necesidad de utilizar un plugin (típicamente, Flash). El problema está en los navegadores: cada uno soporta un formato/codec diferente. Si queremos garantizar que todos los navegadores podrán tratar con nuestro vídeo, hay que preparar múltiples fuentes.
    Ejemplo (c&p de la Wiki ;-)

    <video poster="movie.jpg" controls>
    <source src='movie.webm' type='video/webm; codecs="vp8.0, vorbis"'/>
    <source src='movie.ogv' type='video/ogg; codecs="theora, vorbis"'/>
    <source src='movie.mp4' type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'/>
    <p>This is fallback content</p>
    </video>

    Si queremos dar soporte a navegadores antiguos que no entiendan el tag , debemos preparar un mecanismo para mostrarles el vídeo a través de un plugin.

  • Lo mismo es aplicable para el audio. Cada navegador funciona diferente.
  • Gráficos 2D: se estandariza el tag y su API. Un bloque canvas sólo define una región que puede ser "pintada" a través de un script (típicamente Javascript). Nada más (y nada menos).
    Últimamente se ven algunas maravillas como juegos, pruebas de concepto, etc, "programados en HTML5". Error de concepto: están programados en Javascript y se muestran en una página HTML5.
    Hacer cosas en un no es una tarea trivial. En la especificación se definen funciones o métodos de bajo nivel y nada más. Existen bastantes "librerías" de Javascript que facilitan la tarea, pero no son parte de la especificación.
  • Por fin se simplifican los DOCTYPE (se queda un simple ) y los atributos del tag .

martes, 22 de diciembre de 2009

Enlaces y "popups" accesibles

A veces nos surge la necesidad de forzar que un enlace se abra en una ventana nueva o "popup". El problema es que debemos mantener el HTML válido para que estos enlaces sean seguidos por motores de búsqueda o puedan ser utilizados por navegadores sin Javascript.

En todos los sitios se ve el típico tutorial que dice "... utiliza el window.open() de Javascript ...". Efectivamente, hay que usarlo, pero sin obstruir ni hacer HTML chapucillas.

Vamos a presentar 3 formas de abrir un "popup", una "mala" y dos mejores.
  1. Con HTML "guarrete":
        onclick="window.open('http://davidasorey.net',
            'popup1', 
            'height=800,width=600,resizable=1,scrolling=1')">Enlace 1

    Fatal. El destino de este enlace está "falseado". A veces también nos podemos encontrar algo como:
    <a href="void(0)">
    Esta forma de hacer los "popups" puede ser aceptable sólo si explícitamente queremos que este enlace no se siga.

  2. Un poco mejor, dejando un href correcto:

    <a href="http://davidasorey.net/" target="popup2"
        onclick="window.open('', 
            'popup2', 
            'height=800,width=600,resizable=1,scrolling=1')">Enlace 2</a>
    
    
    Aquí jugamos con el atributo "target". El evento "click" abre una ventana "vacía" pero con un identificador "popup2". Cuando el navegador va a abrir el enlace, ya encuentra una ventana con este identificador y abre el enlace en esta nueva ventana.
    Es una mejora, desde luego: el enlace ya es un enlace "real" e indexable.

  3. Una solución sin Javascript "inline":

    <a class="popup3" href="http://davidasorey.net/">Enlace 3</a>

    Obviamente, esta es la solución más limpia, pero requiere un poco más de trabajo adicional.
    Con jQuery (se puede hacer con cualquier otra librería o "framework") hay que "capturar" todos los enlaces con la clase "popup3" y alterar su comportamiento:
    $(document).ready(function (d) {
     $('a.popup3').click(function (e) {
      e.preventDefault();
      var href = $(this).attr('href');
      window.open(href, 
                 'popup3', 
                 'height=800,width=600,resizable=1,scrolling=1');
     });
    });

    En pocas líneas alteramos el comportamiento de todos los enlaces con la clase 'popup3'. Nos ahorramos andar escribiendo el código Javascript en el evento onclick y todo el código y control de este comportamiento está en un sólo punto.

martes, 21 de julio de 2009

Selectores CSS: la intersección entre diseño y programación

El uso de selectores CSS en los "frameworks" para JavaScript más conocidos ha permitido lo que hace unos años era impensable: los diseñadores entienden el código de los programadores y los programadores pueden comprender el CSS de los diseñadores.

jQuery es especialmente adecuado: no distingue entre selectores "por id" ni selectores por "clase" (Prototype tiene dos funciones diferentes, $() y $$(), para estas tareas).

$('.lateral a.externo').click(function(ev) {
// Código  a ejecutar
})


Este sencillo código es fácil de entender y un diseñador al verlo rápidamente intuye que en todos los tags A dentro de la "capa" lateral que tengan la clase externo se hará algo al hacer click.

.lateral a.externo {color: red; padding: 5px; margin: 0;}


Este fragmento de CSS afecta a los mismos elementos que el javascript anterior. Un desarrollador enseguida ve también a qué elementos afecta.

Lo bueno es que si el diseñador y el programador hablan en el mismo lenguaje (selectores CSS), el desarrollo es muy fácil y se puede trabajar en paralelo fácilmente.

martes, 9 de junio de 2009

"Benchmarkeando" navegadores

En la siguiente dirección, la gente que desarrolla el motor de JavaScript para el navegador Chrome tiene una serie de pruebas para medir el rendimiento de JavaScript de un navegador.

Como es previsible (es parte interesada, ¿no?), Chrome obtiene la mejor puntuación y el nuevo Safari 4 también obtiene una muy buena puntuación.

Lo curioso es que el resto de navegadores sacan mucha peor puntuación, como un orden de magnitud menos. A ver si busco más "benchmarks" de JavaScript y paso otra batería de pruebas.

[gallery link="file" columns="4"]

También he incluido una captura de los IE 7 y 8 a título anecdótico. Se cuelgan en el test.

Actualizacion: he pasado el conjunto de "tests" que proponen en la web de Dromadeo con algunos navegadores. Ahora es Safari 4 el que gana, despues, Chrome.

Actualización II: Firefox 3.5beta gana bastante en rendimiento.

lunes, 16 de febrero de 2009

jQuery vs Prototype

La aparición de estas librerías o "frameworks" para programar JavaScript han supuesto un ahorro de tiempo importante para los desarrolladores. Ya no hay que estar tan pendiente de las incompatibilidades entre navegadores, simplifican mucho el trabajo con el árbol DOM, etc.

Empecé a trabajar con Prototype un poco por "imposición técnica" y ya me pareció una maravilla el poder abstraerme de los XmlHttpRequest y demás. Desde hace un tiempo estoy empezando a profundizar más en jQuery, y, en mi opinión, me parece más cómodo y sencillo.

  • En jQuery los selectores utilizan la sintaxis de CSS y son uniformes, es más claro -- p. ej.: $('#titulo') ó $('div.resaltado') en Prototype serían $('titulo') y $$('div.resaltado').

  • En jQuery las operaciones típicas como ocultar/mostrar un elemento o conjunto de elementos son más sencillas: da igual que nuestro selector nos devuelva un sólo elemento o un conjunto de ellos, el método es el mismo -- p. ej.: $('#titulo').hide() ó $('div.resaltado').hide()

  • Generalizando mucho, podemos decir que jQuery es más conciso y Prototype más explícito.