Introducción a (la programación en) Mathematica

Definición de funciones

Declaración de funciones

Mathematica acepta dos modos distintos de definir una función, mediante = y mediante :=. Veamos la diferencia.

Para definir una función con =, se debe especificar el nombre y la variable de entrada:

f[x_]=x^2

f[3]

f[x] también se puede escribir f@x.

f@4

o

4//f

La función puede depender del número de variables que deseemos y cada variable puede ser del tipo que queramos

g[x_,n_]=x^n

El otro modo de definir una función, más similar a como pensamos en matemáticas, es con := . La sintaxis es la misma.

h[x_]:=x^2

h[3]

¿Cuál es la diferencia entre las dos funciones? Si la función se define con =, trata de evaluarla en el momento en el que se define. En particular, todas las variables se deben poder evaluar. Mientras que si se define con := no se evalúa hasta que no se "usa", es decir, hasta que no aparece su nombre de nuevo. Para verlo con más detalle, usaremos una variable adicional.

a=7;

f[x_]=x+a;

g[x_]:=x+a;

f[3]

g[3]

Si cambiamos el valor de a, podemos ver que en f no cambia (porque se evaluó al definirla), mientras que en g toma el último valor.

a=1

f[3]

g[3]

¿Cuándo usar cada una? La asignación = es más eficiente en tiempo, ya que evalúa la expresión una vez, mientras que := está continuamente evaluando. Sin embargo, la que nos resultará más sencilla de usar es := .

Funciones vectoriales

Recordemos que los vectores en Matematica se escriben con {} y para elegir las coordenadas [[]]

v={4,1}

v[[1]]

Así podemos definir una función vectorial de dos maneras :

f1[w_]:=w[[1]]+w[[2]]

f2 [{x_,y_}]:=x+y

(Nótese que f1 sólo se puede definir con := )

f1[v]

f2[v]

También podemos definir la función tomando como entradas las coordenadas y aplicarla a un vector. Para ello usamos la función Apply (o su abreviatura @@).

f3[x_,y_]:=x+y;

Apply[f3,v]

f3@@v

La función Plus suma elementos. Así que la función f3 es un caso particular de la función Plus.

Plus[2,2]

Podemos aplicar Plus a un vector/lista con @@ para sumar todos sus elementos.

Plus@@v

Podemos transformar cualquier operación en vectorial aplicandola a cada elemento de un vector/lista mediante el comando Map (o su abreviatura /@)

Puntos={{1,2},{3,4},{-1,2},{4,2},{5,1}};

Map[f1,Puntos]

f2/@Puntos

Por último, comentar que el primer elemento de una lista se puede obtener también con First (y el último con Last).

First[Puntos]

Last[Puntos]

y el número de elementos lo obtenemos con Length

Length[Puntos]

Funciones recursivas

La definición de una función de modo recursivo es uno de los recursos más poderosos de los lenguajes de programación. En Mathematica es muy simple definirlas : sólo hay que definir el caso inicial y el general.

Vamos a definir el factorial :

factorial[0]=1;
factorial[n_]:=n factorial[n-1];

(Nótese que debemos usar :=, pues en caso contrario intentaría calcular el factorial de modo recursivo de modo simbólico, lo que no es posible)

factorial[100]

Si tenemos más de un caso inicial, basta definirlos. Por ejemplo, en la sucesión de Fibonacci :

fibonacci[1]=1;
fibonacci[2]=1;
fibonacci[n_]:=fibonacci[n-1]+fibonacci[n-2]

fibonacci[10]

Nuestra función es muy lenta.

Timing[fibonacci[30]]

Esto se debe a que cada vez que llamamos a la función, esta llama a dos evaluaciones, que a su vez llaman a dos evaluaciones ... Por ejemplo, fibonacci[30] llama a fibonacci[29] y fibonacci[28]. Pero fibonacci[29] también llama a fibonacci[28]. Es decir, fibonacci[28] se calcula dos veces. Podemos evitarnos esto creando la función como "cached functions" es decir, cada vez que calculamos un valor, lo guardamos en memoria. Así, si nos lo vuelven a pedir, usamos el ya calculado. 
  En Mathematica, para definir una "cached function" se utiliza una construcción muy original :

fibonacci[n_]:=fibonacci[n]=fibonacci[n-1]+fibonacci[n-2]

¿Por qué funciona esta construcción? Cada vez que llamamos a fibonacci[n] por primera vez para un n, lo que evalúa es la asignación fibonacci[n] = fibonacci[n - 1] + fibonacci[n - 2]. Es decir, ahora tenemos en fibonacci[n] guardado un valor y la próxima vez usará esto antes que la regla.

Veamos que esto acelera la función :

Timing[fibonacci[30]]

Timing[fibonacci[100]]

Eso sí, si cambiamos la definición de la función, hay que borrar los valores almacenados :

Clear[fibonacci]

Restrinciones en el tipo de variables de entrada

Muchas veces nos interesa que la variable sobre la que se evalúa la función tenga ciertas restricciones, porque la función sólo esté bien definida con esas restricciones, o porque queremos que el resultado sea de un cierto tipo (por ejemplo, para pintar funciones)

Por ejemplo, si queremos definir una función que sólo se aplique cuando uno de los parámetros es par, podemos indicarlo escribiendo ?EvenQ detrás de _. Análogamente, OddQ sirve para declarar que es impar . Vamos a definir recursivamente
 las potencias naturales de un número entero.

P[m_,0]=1;
P[m_,n_?EvenQ]:= P[m,n/2]P[m,n/2];
P[m_,n_?OddQ]:= m P[m,(n-1)/2]P[m,(n-1)/2];

P[7,10]

Una restricción muy útil es NumberQ, que obliga al elemento recibido a ser un punto. A la hora de dibujar las funciones o aplicar métodos numéricos puede ser conveniente usarla.

Por ejemplo, vamos a definir una función que dado xp0, devuelva el valor en t = 10 de la solución de cierta EDO de segundo orden determinada por la CI x[0] = 0, x'[0] = xp0.

f[xp0_?NumberQ]:=Block[{x,t},First[x[10]/.NDSolve[{x''[t]+Sin[x[t]]==0,x[0]==0,x'[0]==xp0},x,{t,0,10}]]]

Plot[f[xp0],{xp0,0,2}]

Sin quitamos ?NumberQ, tratará de evaluar la función con la variable simbólica xp0 y dará un error, pues el método numérico requiere valores iniciales reales.

Funciones anónimas

En muchas ocasiones, necesitamos usar funciones de modo puntual, que no volveremos a utilizar. Para definirlas usaremos & para indicar el final de la función y # para indicar la variable.

(#^2&)[4]

Los paréntesis anteriores sobran, así que suele escribir

#^2&[4]

o (en forma de jeroglífico)

#^2&@4

Si queremos una función de varias variables, las denotaremos #1, #2, ...

#1+#2&[4,1]

Para aplicarlo a un vector/lista recordemos que podemos usar @@

#1+#2&@@{4,1}

O bien, definirla usando las coordenadas

#[[1]]+#[[2]]&@{4,1}

De este modo, podemos aplicarla a una lista de puntos :

#[[1]]+#[[2]]&/@{{1,2},{3,4},{-1,2},{4,2},{5,1}}

Las funciones anónimas se usan profusamente. Por ejemplo, la función Select, que permite seleccionar elementos de una lista que cumplan un criterio, necesita recibir una función que se aplique a cada elemento de la lista y devuelva verdadero o falso. En este caso es muy conveniente usar funciones anónimas.

Select[{1,2,4,7,6,2},#>2&]

Ejercicios

1. Crea una función que reciba un vector de dimensión dos e intercambie las dos coordenadas y cambie el signo de la primera.

2. Utilizar “cached functions” para mejorar la función de cálculo de potencias. Comparar los tiempos.

3. Crea una función que reciba una lista de puntos y devuelva el centro de masas.

4. Crea una función que reciba un elemento y cuente cuántas veces aparece en una lista (para comprobar igualdades se usa ==).

5. Usando la función anterior, crea una función que reciba una lista y devuelva una lista formada por parejas con cada elemento de la lista original y cuántas veces aparece dicho elemento.

6. Crea una función que reciba la lista de elementos con la frecuencia de cada elemento y devuelva la mediana de la lista original. Necesitarás usar Ordering (consulta la ayuda).