5 de diciembre de 2022

Conferencia "Arte y ciencia", de Carlos Briones - [Arte y ciencia]

Dentro del ciclo de conferencias de la Fundación Canal, el científico del CSIC y escritor Carlos Briones nos presenta esta maravilla de presentación en la que, gracias a su museo virtual, hace un repaso magnífico de cómo la historia del cosmos puede ser contada a través de la representación pictórica de los cuadros.

Desde el origen del universo en el Big Bang, pasando por la formación de los primeros núcleos a los primeros elementos consecuencia de las reacciones nucleares en las estrellas; de la formación de la Tierra a la aparición del agua y el origen de la vida en la misma; de las extinciones masivas y la aparición de los primates que acabaron evolucionando en el homo sapiens y de cómo éste, gracias al desarrollo del cerebro y la capacidad intelectual, ha sido capaz de entender y transformar el mundo. Y de la importancia de conocerlo lo mejor posible para poder cuidar de él y preservarlo.

En esencia, de cómo el arte y la ciencia van de la mano en el desarrollo del ser humano, sin olvidar el tercero en discordia de esta relación, las humanidades, de modo que en su conjunto constituyen los pilares de la cultura:

Σ(A + H + C)=Q

donde:

  • A: Arte
  • H: Humanidades
  • C: Ciencia
  • Q: Cultura

Esta conferencia me parece el prólogo perfecto para la serie de artículos que quiero desarrollar bajo el nombre de "Arte y Ciencia", para recalcar la necesidad de no identificarse a uno mismo únicamente de ciencias o de letras, sino de la cultura.

16 de noviembre de 2022

Metaheurística y algoritmos evolutivos. Breve introducción y el problema del viajante

Metaheurísticas. Dame soluciones aceptables y no te líes.

Muchos problemas a los que estamos acostumbrados se nos presentan con una ecuación o varias ecuaciones y ante las condiciones que configuran el problema nos disponemos a evaluar la expresión, realizar un par de ajustes o cálculos por aquí y por allá, y así poder obtener finalmente el resultado solicitado. Como por ejemplo en la ecuación de la posición de la partícula en el estudio cinemático del movimiento:

Sin embargo, la gran mayoría de problemas que se nos puedan presentar en la realidad del día a día tienen unos inconvenientes que hay que tener en cuenta

  • No son sencillos de modelar matemáticamente
  • Aún teniendo una expresión matemática que los modele (con mayor o menor fidelidad con la realidad), el requerimiento de potencia computacional para resolverlos es excesivamente elevado
  • Es posible, además, que el problema no tenga una solución (pero que aún así sea relevante encontrar la mejor aproximación posible a la solución)

Es decir, puede que hayamos conseguido sintetizar las necesidades de nuestro problema y haber planteado un conjunto de ecuaciones que lo modele, pero si el tiempo y energía necesarios para poder realizar los cálculos sobrepasa unos ciertos límites, el resultado pierde valor por muy preciso que sea.

Por ejemplo, pensemos en los sensores de posición que tiene un coche y que nos avisan con un pitido cuando estamos aparcando si nos aproximamos demasiado a un obstáculo. El programa que procesa la información que le aportan esos sensores realiza una serie de cálculos para determinar que en efecto un objeto se encuentra en las proximidades de la carrocería de nuestro coche, pero ¿hasta qué punto es necesario que dicho cálculo sea preciso y haga un mapeo exhaustivo de todo el detalle que captan las cámaras y determine exactamente todas las posiciones relativas de todos los elementos respecto al coche?. Recordemos que estos cálculos se realizan mientras aún estamos dando marcha atrás, y si los cálculos que han de activar el pitido que nos avisa tardan demasiado, en vez de escuchar el pitido, escucharemos el golpetazo contra el coche de detrás.

Lo que la mayoría de estos sistemas hacen es realizar una serie de cálculos que, sin buscar la solución exacta del problema, nos aporten una solución lo suficientemente buena en un tiempo de respuesta rápido.

Hablaremos por lo tanto de técnicas metaheurísticas cuando nos refiramos a procesos que apliquen este tipo de funcionamiento lógico a la hora de buscar soluciones. En las que, bien por la complejidad de resolución del problema o por los recursos necesarios para hacerlo, es preferible desarrollar un modelo aproximado y de poco consumo que ofrezca resultados aceptables.

Y recordemos, hay ocasiones en que dicha solución exacta no existe.

El siguiente gráfico nos muestra una recopilación de diferentes técnicas metaheurísticas que se emplean para la resolución de problemas complejos:

https://web.archive.org/web/20170211142120if_/http://nojhan.free.fr/metah/images/metaheuristics_classification.jpeg

Algoritmos evolutivos. Una receta darwiniana para buscar soluciones.

Como vemos en la imagen de arriba, existen muchos y distintos tipos de técnicas para buscar soluciones buenas y aproximadas para problemas de dificil solución. Una de estas técnicas son los algoritmos evolutivos. Vamos por partes:

  • Un algoritmo es un proceso que repite una serie de pasos a lo largo de varias iteraciones.
    • Un ejemplo cotidiano de algoritmo podría ser una receta de cocina, donde tenemos una serie de pasos a seguir para poder elaborar nuestra comida (cortar en juliana la verdura, sofreir en la sartén, añadir el caldo y cocer removiendo cada 5 minutos...).
    • Otro ejemplo de algoritmo sería el proceso de división que nos enseñan de pequeños en el colegio, y en el que mediante pasos iterativos vamos construyendo el "árbol" debajo del dividendo y que nos permite obtener el cociente de la división y el valor del posible resto.

Vale, está claro, ¿pero qué pinta Darwin aquí? 

  • La teoría de la evolución nos explica cómo los diferentes entes biológicos que componen (o han formado parte) del mundo han ido evolucionando, cambiando, a lo largo de los años hasta tener la forma que conocemos hoy día.
  • Por otra parte, la selección natural es el proceso evolutivo que nos explica que aquellos individuos que tenían mejores características para adaptarse al medio en que vivían eran los que más posibilidades tenían de sobrevivir y, por lo tanto, de reproducirse y perpetuar sus características dentro de la especie.

Entonces, ¿qué es un algoritmo evolutivo?

Un algoritmo evolutivo es aquel que se basa en los principios de la evolución y la selección natural para, a lo largo de las distintas iteraciones, optimizar sus resultados generando soluciones de una iteración a la siguiente utilizando aquellos valores que mejores soluciones han aportado hasta el momento, simulando el comportamiento de las especies naturales en los procesos biológicos de evolución.

Quizá la mejor manera de ilustrar el funcionamiento básico de los algoritmos evolutivos sea con un ejemplo, el del problema del viajante.

En el problema del viajante (TSP - Travelling Salesman Problem en inglés), nos encontramos ante el problema de secuenciación que ha de resolver un comercial que tiene que recorrer una serie de ciudades sin repetir el paso por ninguna de ellas, regresando en su último viaje a la ciudad de origen e invirtiendo el menor tiempo posible en su recorrido.

Supongamos un caso sencillo, con tres ciudades A, B, C para las que tenemos los siguientes tiempos necesarios para ir de una a otra:

  • A → B = 5
  • A → C = 1
  • B → A = 5
  • B → C = 4
  • C → A = 7
  • C → B = 8
Y los posibles resultados que nos podríamos encontrar serían:
  • A → B → C → A = 5 + 4 + 7 = 16
  • A  C  B  A = 1 + 8 + 5 = 14
  • B  A  C  B = 5 + 1 + 8 = 14
  • B → C  A  B = 4 + 7 + 5 = 16
  • C  A  B  C = 7 + 5 + 4 = 16
  • C  B  A  C = 8 + 5 + 1 = 14

Para los datos de este ejemplo, vemos que hay tres opciones que le suponen un menor consumo de tiempo, por lo que en función de tener que salir desde A, B o C, nuestro viajante ya podría saber cuál sería la ruta más óptima en cada caso.

Supongo que te habrás percatado, pero a medida que aumentemos el número de ciudades las combinaciones posibles aumentan considerablemente. En particular, para este caso el total de posibles recorridos corresponde con el factorial del número de ciudades.

  • 1! = 1
  • 2! = 2
  • 3! = 6
  • 4! = 24
  • 5! = 120
  • 6! = 720
  • 7! = 5040
  • 8! = 40320
  • 9! = 362880
  • 10! = 3628800

Es decir, si tuviéramos que evaluar el posible recorrido óptimo para 10 ciudades tendríamos que evaluar 3.6 millones de posibilidades para luego elegir la mejor, imagínate cuántas harían falta para 20, 30 o 100 ciudades (de hecho, no lo imagines, calcula el factorial) .

Es aquí donde los algoritmos evolutivos vienen al rescate y nos permitirán obtener una solución buena (o incluso la mejor) ahorrándonos tiempo de cálculo y quebraderos de cabeza. Dentro de las diferentes técnicas que abarcan los algoritmos evolutivos, vamos a plantear de manera esquemática cómo afrontar este problema haciendo uso de un algoritmo genético.

Estos algoritmos se basan en los conceptos de genética y los mecanismos evolutivos y de selección natural para ir mejorando las soluciones encontradas a lo largo de las diferentes iteraciones que se realizan. Esto quiere decir que no es necesario evaluar todos los posibles resultados para luego elegir el mejor. Por el contrario, generaremos un número reducido de posibles resultados de manera aleatoria, y en cada iteración elegiremos los que mejores resultados nos han dado para combinarlos entre sí y generar una nueva tanda de posibles soluciones, confiando en que al heredar las propiedades de las mejores soluciones previas, los nuevos resultados generados nos den un resultado mejor, de modo que iteración tras iteración nos acerquemos a una solución al problema lo más óptima posible.

Las soluciones que se evalúan en cada paso del algoritmo se asemejan a genes o cromosomas. Por ejemplo, para un problema con 7 ciudades (A, B, C, D, E, F, G), algunas posibles soluciones serían las siguientes, donde la secuencia de elementos nos indica el orden en el que se visitará cada ciudad:

  • (A, B, C, D, E, F, G)
  • (A, C, B, D, E, F, G)
  • (C, D, E, F, G, A, B)
  • (F, G, A, C, D, E, B)
  • ...
Por lo tanto el primer paso de nuestro algoritmo es construir una población inicial de individuos (soluciones) incial desarrolladas normalmente de forma aleatoria pero asegurándonos de que el resultado sea una solución válida (en este caso, que no pase 2 veces por la misma ciudad y que las recorra todas)

Una vez lo tenemos, comienza el proceso evolutivo, y a lo largo de las diversas generaciones de nuestra población (las iteraciones del algoritmo) iremos evolucionando nuestro conjunto de soluciones de la siguiente manera:

  1. Seleccionar las mejores soluciones de la generación anterior. Este proceso puede abordarse de varias maneras en f unción de la configuración del problema y del algoritmo en cuestión.
  2. Para el conjunto de individuos escogidos en el punto anterior, se realizarán una serie de combinaciones de cruce entre dichos "padres" que darán lugar a los "hijos" que compartirán la información genética de ambos progenitores. Es posible que alguna de estas soluciones generadas experimente algún tipo de mutación que varíe ligeramente el resultado obtenido tras el cruce.
    • Cruce (crossover): se realiza una mezcla de los cromosomas de las dos soluciones padres que generarán una o dos soluciones hijo. Aunque existen distintos tipos de formalismos de cruce, cada problema presenta unas características específicas que han de tenerse en cuenta para evitar generar soluciones no factibles (aunque podrían permitirse, pero nos haría perder tiempo al generar soluciones malas)
    • Mutación: se modifica al hazar algún componente de alguna de las soluciones generadas (por ejemplo, intercambiar el orden de paso por las ciudades A y G). Los beneficios de realizar mutaciones (en baja proporción) es que permite amplicar las posibles soluciones que aún no se han visitado, ya que según se haya diseñado el proceso de cruce cabe la posibilidad de "quedarnos encerrados" en el mismo nicho del espacio de búsqueda.
  3. Actualizar la población actual para generar la nueva lista de "padres" para la siguiente generación. Aquí pueden plantearse varias opciones:
    • Erradicar totalmente la generación previa y únicamente quedarnos con la nueva generación de soluciones (asumiendo el riesgo de perder buenas soluciones si las soluciones generadas no son mejores que las anteriores)
    • Mezclar en la población resultante a los mejores individuos de la población de padres y la población de hijos (nos permite mantener las mejores soluciones, pero con el riesgo de perder diversidad y que podamos quedarnos atascados en una solución óptima a nivel local)
    • Mantener una lista de "las mejores soluciones" paralela a la población de de la generación actual, lo que nos permite no perder las mejores soluciones encontradas hasta el momento
  4. Evaluar nuestras soluciones para ver cuáles son las mejores.
Tras el número de iteraciones que se desee realizar, este proceso irá evolucionando a nuestra solución hasta darnos con un conjunto de soluciones resultados que en ocasiones serán los resultados óptimos, y en su defecto serán muy buenas aproximaciones a la mejor solución.

El concepto de algoritmo genético fue introducido por John Holland en 1975 en su libro Complex Adaptive Systems. Adaptation in Natural and Artificial Systems An Introductory Analysis with Applications to Biology, Control, and Artificial Intelligence

La siguiente imagen nos muestra el pseudo código de un algoritmo genético tal y como Holland lo planteó y que se asemeja el proceso que hemos descrito más arriba.


Sé que puede que para algunos este contenido sea un poco complicado, y que a otros les haya sabido a poco. En futuras entradas hablaré más en detalle de algún ejemplo de algoritmo y sus elementos aplicados a la resolución de problemas concretos que espero sean de interés.


Referencias

Si quieres ver un paper real donde se afronta la solución del problema del viajante:

Para saber más sobre técnicas y procesos de optimización, en el siguiente enlace puedes encontrar un magnífico (y largo) libro escrito por Thomas Weise:

2 de noviembre de 2022

¿Quiénes somos? ¿De dónde venimos? ¿Adónde vamos? - Siniestro Total - [🎵 Música y ciencia]

La ciencia y la filosofía tienen, quizá, su raíz fundamental en el cuestionamiento de preguntas: ¿Por qué esto? ¿A qué se debe aquello? ¿Cómo funciona esta cosa?. 

Este tema de Siniestro Total es todo en sí un compendio de preguntas que lleva planteándose el ser humano desde que tiene uso de razón, y para las que aún seguimos sin tener una certeza de sus respuestas, lo que no nos impide disfrutar de este temazo.

🎵 ¿Quiénes somos? ¿De dónde venimos? ¿Adónde vamos?
👥 Siniestro Total
💿 Menos mal que nos queda Portugal (1984)

¿Cuándo fue el Gran Estallido?
¿Dónde estamos antes de nacer?
¿Dónde está el eslabón perdido?
¿Dónde vamos después de morir?
¿Qué son los agujeros negros?
¿Se expande el universo?
¿Es cóncavo o convexo?

¿Quiénes somos?
¿De dónde venimos?
¿Adónde vamos?
¿Estamos solos en la galaxia o acompañados?
¿Y si existe un más allá?
¿Y si hay reencarnación?

¿Quiénes somos?
¿De dónde venimos?
¿Adónde vamos?
¿Estamos solos en la galaxia o acompañados?

¿Qué es el ser?
¿Qué es la esencia?
¿Qué es la nada?
¿Qué es la eternidad?
¿Somos alma?
¿Somos materia?
¿Somos sólo fruto del azar?
¿Es fiable el carbono 14?
¿Es nuestro antepasado el hombre de Orce?

¿Quiénes somos?
¿De dónde venimos?
¿Adónde vamos?
¿Estamos solos en la galaxia o acompañados?
¿Y si existe un más allá?
¿Y si hay reencarnación?

19 de octubre de 2022

Números primos

¿Qué es un número primo? 

Un primo es un número natural (mayor que 1) que únicamente tiene dos divisores: el 1 y a sí mismo. Por ejemplo:

  • 2 es primo (y de hecho es el único par)
  • 3 es primo (y junto al 2 son los únicos números primos consecutivos, lo cual es lógico ya que el 2 es el único par)
  • 4 no es primo, porque es divisible por 1, 2 y 4. En este caso, se llaman números compuestos (de nuevo a excepción del 1)
  • 5 es primo, 6 no, 7 sí,... 199 sí, ... 983 sí,... y así hasta el infinito

 

El número 1 no es primo

Hemos comentado que el número 1 no es considerado primo, ¿por qué?

En el fondo se trata de una convención, ya que si fuésemos riguroso con la definición de números primos, en efecto sólo es divisible por sí mismo y por la unidad (de nuevo, sí mismo). De hecho, hasta el siglo XIX sí que se consideraba primo al número 1.

El hecho de considerar el 1 como no primo permite una formulación simplificada del teorema fundamental de la aritmética, que nos dice que:

Todo entero positivo n > 1 puede ser representado exactamente de una única manera como un producto de potencias de números primos

Por ejemplo:

 

 
Propiedades de los números primos

Algunas características de los números primos son:

  • En el sistema decimal, salvo el 2 y el 5, todos los números primos terminan en 1, 3, 7 o 9
  • Excepto el 2 y el 3, todo número primo puede representarse de la forma 6n+1 o 6n-1 (donde n es un número natural) 
  • Excepto el 2, todo número primo puede representarse de la forma 4n+1 o 4n-1 
  • Lema de Euclides: sean a y b dos números enteros de modo que su producto ab sea divisible por un número primo p implica que p es divisor de a o de b. 
  • Teorema de Wilson 
Un número p > 1 es un número primo si y sólo si se cumple que
    • (p - 1) ! ≡  -1 mod p
    • (p - 1) ! ≡  (p-1) mod p  
La función mod (módulo) nos devuelve el resto de la operación. Para el caso concreto del Teorema de Wilson podemos verlo mejor con un ejemplo, donde comprobamos que para un número primo, el resto calculado coincide con el número a evaluar -1: 

 

O expresado de otra manera (que personalmente me gusta más, y es la que programaremos más adelante)

 

  • Pequeño teorema de Fermat. Si p es un número primo, y a es un entero positivo mayor que 1 menor que p (es decir, 1<a<p)  
ap-1 ≡ 1 (mod p) 

O en otras palabras, si a es un entero positivo y p es un primo que no divide a a, entonces p debe ser un factor de ap-1-1. Veamos algunos ejemplos:


 

¿Cómo determinamos si un número es o no primo? Tests de primalidad

Acabamos de ver ciertas propiedades que a priori nos permitirían poder deter minar si un número es o no primo. En efecto, aplicarlas es más o menos sencillo para números pequeños, e incluso relativamente medianos. Pero cuando nos topamos con número grandes lo de ir comprobando si es divisible o no por todos los primos previos al número se convierte en una tarea ardua (incluso para los ordenadores más potentes). Para hacernos una idea, el número primo más grande conocido en la actualidad tiene la friolera de 24862048 cifras.

Existen varios enfoques para abordar la determinación de si un número es o no primo (esta propiedad se conoce como la primalidad del número). y dichos procedimientos se conocen como tests de primalidad. Estos tests se tratan de algoritmos que intentan verificar si un número dado es compuesto, y en función de lo que consigamos evaluar tenemos dos posibles resultados

Se demuestra que es compuesto, y por lo tanto, no es primo.

Non conseguimos demostrar que sea compuesto, por lo que ante la falta de una mejor certificación de que el número dado no sea compuesto podemos tener un cierto nivel de confianza de que se trate de un número primo.

Hemos que tener en cuenta que las operaciones ariméticas a realiar en los diversos procedimientos que comentaremos ahora suponen un consumo considerable de recursos de la máquina efectuando los cálculos (en especial cuando intervienen operaciones como el cálculo de factoriales que veíamos antes en el Teorema de Wilson, que hace que el tiempo necesario para la evaluación de números muy grandes sea prohibitivo.


La criba de Eratóstenes

Se trata del primer procedimiento matemático conocido para la determinación de este tipo de números (data del siglo 2 a.e.c, aunque existen referencias a los números primos anteriores a la de este método). El método consiste en confeccionar una lista (entre el número 2 y el número máximo que queramos evaluar) en la que se identifiquen todos los números primos contenidos en la misma.

El proceso para determinar estos números consiste en, de manera iterativa, ir "tachando" y marcando como compuestos todos los multiplos que sucedan al último número primo que hayamos encontrado. Veámoslo mejor con un ejemplo (supongamos que se nos pide encontrar todos los primos hasta el número 35:

  • El primer número primo que nos encontramos es el 2, así que tachamos todos sus múltimos (todos los números pares)
  • El siguiente sería el 3, así que tachamos todos los múltiplos siguientes (el 6 ya estaría tachado al ser múltiplo de 2, el 9, el 12 tachado previamente, el 15,...)
  • El siguiente número es el 4, que al estar tachado por el 2 no es primoç
  • Seguimos por el 5, que es primo, y tacharíamos por tanto todos sus múltiplos en la lista
  • ...
  • Así hasta terminar con la siguiente lista: 2, 3, 5, 7, 11, 13, 17, 19, 23, 19, 31.

Veamos una posibilidad de programar este método en R:

  • Al inicio discernimos si se trata de 0 o 1 (no primo) o 2 (primo) evaluado directamente.
  • Inicializamos una variable de iteración i=2.
  • Creamos un vector vacío (la criba que iremos rellenando a modo de listado) donde cada elemento estará rellenado con una cadena de caracteres vacía (esto lo conseguimos inicializanod el vector con la función vector(mode = "character" ,val-1), donde el segundo parámetro val-1 será la longitud del mismo (-1 porque lo empezamos en el valor 2).
  • Iteramos con un bucle while hasta que nuestro iterador tenga el mismo valor que el valor que queremos determinar si es primo o no.
  • Si lo es (i==val) devolvemos TRUE. Enhorabuena, el número es primo
  • En caso contrario, miramos lo que contiene el vector con la criba en la posición correspondiente. Si está vacío quiere decir que es un número primo entre 2 y nuestro valor, y lo marcamos con la letra P.
  • Además, en este caso, iteramos sobre el resto de elementos de la lista empezando en j=i+i (para acceder al siguiente elemento de la lista múltiplo del primo intermedio que acabamos de encontrar, iterando la lista mientras j sea menor o igual que nuestro número a determinar. Donde en cada caso podemos tener que:
    • j sea igual a nuestro número, es decir, es un múltiplo del último primo que hemos encontrado y por lo tanto se trata de un número compuesto, por lo que devolvemos FALSE. Lástima, no es primo.
    • j tenga cualquier otro valor, por lo que dicho elemento será un número compuesto múltiplo del primo que acabamos de descubrir, y lo marcamos como C, para evitar considerarlo en futuras iteraciones.

 

El pequeño teorema de Fermat

Tal y como hemos visto en las propiedades de los primos, el pequeño teorema de Fermat nos dice que si p es un número primo, y a es un entero positivo mayor que 1 menor que p (es decir, 1<a<p)  

ap-1 ≡ 1 (mod p) 

Y para programarlo en R podemos hacer lo siguiente:

[IMPORTANTE] antes de nada, como este método utiliza exponenciales (es decir, tendremos números que se harán grandes) hemos de ser conscientes de las limitaciones que tiene R a la hora de operar con números de gran tamaño (otros lenguajes de programación tendrán problemas similares). En nuestro caso, R tiene limitado el tamaño de los enteros con los que puede operar. Para ver cuál es dicho valor podemos ejecutar en la consola el comando .Machine$integer.max, que en mi caso me devuelve el valor 2147483647. Para solventar este problema, podemos hacer uso de la clase bigz que se encuentra dentro del paquete gmp

En el ejemplo de la programación veremos cómo utilizarlo para curarnos en salud cuando los números se hagan excesivamente grandes. Así que ahora sí, vamos a ver cómo programar este pequeño teorema de Fermat: 

  • Nuestra función recibe dos parámetros. Por un lado el valor (val) que queremos determinar si es o no primo, y por otro el parámetro a que usaremos como base de la exponencial
  • Lo primero de todo, determinar trivialmente si se trata de un no primo en caso de evaluar el 0 o el 1, o en caso de tratarse del 2, devolver TRUE pues es primo.
  • Sabemos que dicho valor a ha de cumplir una serie de condiciones: ser menor que p y no ser divisor de este (lo que supondría que el valor a determinar es compuesto). Por lo tanto, en un primer bucle while vamos a determinar cual es el menor valor de a que satisface esta condición (lo iremos incrementando de 2 en dos salvo en el caso inicial de a = 1 ya que en resto de casos iremos saltando de impar en impar hasta encontrar uno que satisfaga nuestra condición
  • A continuación, y para solventar el problema de números excesivamente grandes que comentábamos en la nota inicial, declararemos una variable auxiliar definida como gran entero (bigz) que será la que utilizaremos en nuestras operaciones
  • Evaluamos la expresión exponencial que dicta el pequeño teorema de Fermat. Si el residuo es 0 tenemos un primo (TRUE), y en caso contrario un compuesto (FALSE)

 

Búsqueda ingenua

Puede que te estés preguntando, ¿y por qué no podemos ir mirando todos los números entre 2 y el que queremos determinar si es primo o no para ver si alguno es divisor de este?.

Pues sí, podemos (aunque cuanto mayor sea el número más tardará en realizar las comprobaciones del residuo de la división.

De nuevo nos podemos encontrar con el problema de tratar con números excesivamente grandes, así que igual que hemos visto en el método anterior, utilizaremos la clase bigz a la hora de programarlo en R:

  • La función tiene dos parámetros: el valor (val) a determinar y un paráetro from_i para determinar el inicio a partir del cual empezar a evaluar los residuos.
  • Como siempre, determinamos la posibilidad de que val sea 0, 1 o 2
  • A consinuación, asignamos la variable auxiliar de tipo bigz para poder lidiar con números excesivamente grandes
  • En un bucle while, iteraremos hasta que la variable de iteración i sea menor o igual que la raíz cuadrada de nuestro valor a evaluar (ya que éste será su máximo posible divisor), y dentro de cada iteración, si determinarmos que el residuo es 0, entonces el valor es compuesto. En caso contrario, incrementamos el iterador una unidad y volveremos a probar.
  • Si salimos del bucle sin haber encontrado un divisor (ya que la raíz del valor será siempre menor que éste, y nunca llegaremos al punto de evaluar la división por si mismo), entonces hemos encontrado que el número no es compuesto.


¿y ya está?

Para nada. Con lo que hemos visto aquí no hemos hecho más que rascar la punta del iceberg del mundo de los números primos. 

  • No hemos visto, por ejemplo, las aplicaciones que tienen los números primos, entre las cuales destacan la seguridad y criptografía.
  • No hemos hablado tampoco del desarrollo de nuevos algoritmos y métodos para determinar la primalidad de números primos más grandes.
  • Ni hemos hecho referencia al estudio de los primos denominados de Mersenne y de cómo la búsqueda distribuida de primos de este tipo es la responsable del descubrimiento de los más recientes grandes primos hallados.

Así que si tienes interés por conocer más acerca de este tipo de números, puedes indagar en los enlaces que te dejo a continuación.

 

Bibliografía y enlaces de interés sobre los números primos