Matemáticas con Mathematica

Asignación y asignación diferida. Listas y reglas de sustitución. Localización de variables. Gráficas y primitivas gráficas. Programación basada en listas y programación funcional.

Francisco Javier Pérez González
Departamento de Análisis Matemático
Universidad de Granada

Los comandos de asignación ( = ) y de asignación diferida ( := )

En Mathematica el símbolo de igualdad (=) es la forma usual de escribir el comando "Set[ ]" (léase atribuye o asigna). Así "expr1=expr2" y "Set[expr1,expr2]" son dos formas de decirle a Mathematica que evalúe inmediatamente expr2 y el resultado de esta operación lo asigne en adelante al símbolo expr1. Es importante distinguir entre el comando de asignación inmediata (=) y el comando de asignación diferida (:=) cuyo nombre es "SetDelayed[ ]". Cuando escribimos "expr1  :=  expr2" le estamos diciendo a Mathematica que asigne "expr1" al valor diferido de "expr2" pero "expr2" no se evalúa inmediatamente sino que para ello hay que llamar a "expr1" y cada vez que lo hacemos "expr2" se evalúa de nuevo. Para que entiendas bien la diferencia, en el siguiente ejemplo uso el comando "Random[ ]" que proporciona un número aleatorio entre 0 y 1. Cada vez que ejecutas el comando obtienes (salvo rara casualidad) un número distinto. Compruébalo.

Random[]Random[]Random[]

Asignamos ahora el símbolo "aleatorio1" a una ejecución inmediata del comando Random[ ]. Dicho comando se ejecutará y el resultado obtenido será en adelante el valor de "aleatorio1".

aleatorio1 = Random[]

aleatorio1

Hacemos ahora una asignación diferida del símbolo "aleatorio2" al comando Random[ ]. El comando Random[ ] no será evaluado. Para evaluarlo deberemos invocar al símbolo "aleatorio2" y cada vez que lo hagamos se ejecutará de nuevo dicho comando por lo que el valor de "aleatorio2" será diferente cada vez que lo llamemos.

aleatorio2 := Random[]

aleatorio2aleatorio2

Habrás observado que cuando se usa el comando de asignación diferida Mathematica no presenta ninguna salida en pantalla. Eso es debido a que dicho comando, al igual que otros, tiene salida Null  que es un símbolo usado por Mathematica para indicar la ausencia de una expresión o resultado como salida en pantalla. Cuando un comando tiene salida Null no se imprime nada en pantalla.

Para definir una función puedes usar la asignación inmediata y también la asignación diferida. Muchas veces no tiene mayor importancia cuál de las dos uses pero a veces sí que la tiene. Observa.

f[x_] = N[x, 30]   f[Pi]

Está claro lo que ha pasado. Al definir f[x] con la asignación inmediata se evalúa enseguida el comando N[x,30] y, como x no es una cantidad numérica, Mathematica da como respuesta el propio x que es lo que de aquí en adelante Mathematica interpretará como f["deloquesea"].

Clear[f]f[x_] := N[x, 30]f[Pi]

Ahora hemos definido f[x] con la asignación diferida por lo que el comando N[x,30] no se evalúa hasta que se llama a la función.

Como estrategia general, es conveniente usar la asignación diferida para definir funciones pero hay algunos casos importantes como, por ejemplo, para definir derivadas, en los que es preferible usar la asignación directa. Vamos a verlo.

Como ya sabes, es muy fácil representar las derivadas de primero, segundo y tercer orden de una función de una variable. Mathematica lo hace igual que tú. Compruébalo.

f[x_] := x^4  + Sin[x^2]f'[x]   f''[x]f'''[x]

Por tanto, definida una función f[x] de una variable Mathematica sabe derivarla y no necesitas definir aparte su derivada f '[x]. Para derivadas de orden mayor que tres se usa el comando "D[f[x],{x,n}]" que proporciona la derivada n-ésima. Hay una sutileza que debes tener en cuenta al definir derivadas. El siguiente ejemplo la pone de manifiesto.

Clear[f]f[x_] := x^2*Tan[x]h[x_] := D[f[x], {x, 5}]h[3]

Al definir h[x] con una igualdad diferida (:=) lo que Mathematica ha almacenado como definición de h[x] no es la derivada quinta de (x^2)Tan[x] sino el comando "D[f[x], {x, 5}]"  pero dicho comando no se ha ejecutado pues se trata de una asignación diferida por lo que dicho comando se ejecuta después de que se llama a la función h[x]. Por eso cuando escribes h[3] lo que estás diciendo a Mathematica es que derive f[3] respecto a la variable 3 cinco veces y, claro está, 3 no es una variable válida para derivar. Naturalmente, esto se arregla definiendo h[x] con la asignación inmediata (=).

Clear[f, h]f[x_] := x^2*Tan[x]\(h[x_] = D[f[x], {x, 5}]; 
h[3]\)

La diferencia está en que ahora Mathematica ejecuta inmediatamente el comando D[f[x], {x, 5}] y su resultado lo asigna a h[x].

Listas

En Mathematica una lista es cualquier expresión que empiece con "{" y termine con "}". Ejemplos de listas son {1,2,3}, {Cos[x],Sin[y]}, {a,{b,c}}, {{1,2},{3,4}}. Una lista es realmente un vector (cuyos elementos pueden ser de muy variada naturaleza) por eso el orden en que se escriben sus elementos es importante.

{1, 2} == {2, 1}

Otra forma de escribir listas es usando el comando List[elementos].

List[a, 1, b, 2]

Los comandos que se usan con mayor frecuencia para generar listas son Table[ ] y Range[ ].

Table[expr, {k}] genera una lista con k copias de expr. 
Table[expr, {i, imax}] genera una lista de los valores de expr cuando i recorre los valores 1,2,... hasta imax. 
Table[expr, {i, imin, imax}] genera una lista de los valores de expr cuando i recorre los valores imin, imin+1,... hasta llegar sin exceder imax. Los valores imin, imax pueden o no ser enteros.
Table[expr, {i, imin, imax, inc}] igual que el anterior pero con incrementos iguales a inc. 
Table[expr, {i, imin, imax}, {j, jmin, jmax}, … ] produce una lista de listas (una lista anidada). Para cada valor del índice i se recorren todos los valores del índice j.
Range[k] genera una lista con los primeros k enteros positivos.
Range[a, b] genera una lista que empieza en a con incrementos de una unidad hasta llegar a b sin excederlo. Los números a y b pueden o no ser enteros.
Range[a, b, inc]  igual que el anterior pero con incrementos iguales a inc.

Table[Sin[x], {3}]Table[2^i, {i, 6}]Table[i^2, {i, 3, 7}]Table[i^2, {i, 3.5, 7}]Table[i^2, {i, 3.5, 7, 0.5}]Table[n^m, {m, 2}, {n, 3.5, 7, 0.5}]Range[5]Range[2.7, 9]Range[2.7, 9,  .8]

Cuando tengas que trabajar con listas de muchos elementos el comando Range[ ] suele ser más rápido que Table[ ].

Timing[Table[i, {i, 200000}]][[1]]   \(\(Timing[Range[200000]]\)[\([1]\)]
 (*\ 
      tiempo\ que\ tarda\ Range[\ ]\ en\ generar\ los\ primero\ doscientos\ 
        mil\ números\ *) \)

Una particularidad de Mathematica extraordinariamente útil es que casi todas las funciones pueden trabajar con listas. Aquí tienes unos ejemplos.

Sin[Range[1. , 2,  .1]]Range[1, 7]^21/Range[1, 7]{Range[1, 3], Range[4, 7]}^2

En general, para aplicar una función a los elementos de una lista, se usa el comando Map[funcion,lista] donde "funcion" debe ser el nombre de la función.

Map[funcion, {a, b, c}]
Map[1/x, {1, 2, 3}]   
inverso[x_] := 1/x
Map[inverso, {1, 2, 3}]

Con frecuencia es preciso sumar o multiplicar los elementos de una lista. Esto puede hacerse con los comandos Apply[Plus,lista] y Apply[Times,lista].

Apply[Plus, Range[1, 100]]   Apply[Times, Range[1, 10]]

A veces interesa controlar el lugar en que se aplica una función. Aquí tienes un ejemplo.

Apply[Plus, {{1, 2}, {3, 4}}]   
Apply[Plus, {{1, 2}, {3, 4}}, 1]

Mathematica 5. 0 y siguientes tiene el comando Total[lista] que calcula la suma de todos los elementos de una lista.

Total[Range[100]]

El comando Length[lista] proporciona el número de elementos de una lista.

Length[Range[23]]Length[Table[Range[k], {k, 7}]]

Una matriz es una lista cuyos elementos son listas simples (las filas) de igual longitud (número de columnas).

A = Table[-i*j + 2  j, {i, 3}, {j, 4}]

Podemos escribir A en forma matricial.

MatrixForm[A]

Podemos sumar y multiplicar listas de igual longitud. El producto de listas se hace elemento a elemento, es decir, se multiplican los elementos que ocupan el mismo lugar en cada una de las listas.

{a, b, c} + {x, y, z}   {a, b, c} {x, y, z}   {{a, b}, {1, 2, 3}} {{2, 3}, {x, y, z}}

Para multiplicar dos matrices con las dimensiones apropiadas se escribe "." entre ellas dos. Para multiplicar matrices debes representarlas como listas y no usar la forma matricial MatrixForm[ ]. En general, para hacer cálculos en los que intervengan matrices hay que representar dichas matrices como listas y no usar la forma MatrixForm[ ]. El comando MatrixForm[ ] solamente sirve para representar una matriz en la forma usual pero no sirve para hacer cálculos.

B = Table[i*j - 2  j, {i, 4}, {j, 3}]

MatrixForm[B]

M = A . B

MatrixForm[M]

Observa la diferencia.

M M   M . M   {a, b, c} {x, y, z}   {a, b, c} . {x, y, z}

Para indicar el elemento que ocupa el lugar k en una lista se usa la sintaxis "lista[[k]]".

{a, b, c, d}[[2]]   A[[1]]

En una lista de listas se usa la sintaxis "lista[[i,j]]" para seleccionar el elemento que ocupa el lugar i de la lista (la fila) y dentro de él el que ocupa el lugar j (la columna).

A[[1, 4]]

El comando Sum[f[k],{k,kmin,kmax}] que suma los valores de f[k] desde k=kmin hasta k=kmax puede usarse para sumar elementos de una lista. Para calcular el producto podemos usar el comando Product[f[k],{k,kmin,kmax}]. Todo lo que hay que hacer es sustituir f[k] por el k-ésimo elemento de la lista: Sum[lista[[k]],{k,kmin,kmax}] o Product[lista[[k]],{k,kmin,kmax}].

El comando Reverse[lista] invierte el orden de los elementos de la lista.

Reverse[{a, b, c, d, e}]

El comando Transpose[listadelistas] intercambia los primeros dos niveles en "listadelistas". Cuando se trata de una matriz cambia filas por columnas. Las listas deben ser de igual longitud.

Transpose[A]

% // MatrixForm

Transpose[{{{1, 2}, {3, 4}}, {5, 6}, {7, 8}}]

El comando Sort[lista] ordena los elementos de una lista. Last[lista] y First[lista] nos dan el último y el primer elemento de una lista. Max[lista] y Min[lista] nos dan el máximo y el mínimo elemento de una lista numérica.

Sort[{u, x, v, c, a, f, e}]    Sort[{0.5, 0.12, 0.23, 0.67, 0.765, 0.07}]    Last[{0.5, 0.12, 0.23, 0.67, 0.765, 0.07}]    First[{u, x, v, c, a, f, e}]     Max[{0.5, 0.12, 0.23, 0.67, 0.765, 0.07}]  Min[{0.5, 0.12, 0.23, 0.67, 0.765, 0.07}]

Map[Sort, {{3, 2, 1}, {6, 4, 7}}]

Una operación importante es la de seleccionar elementos en una lista lo que se hace con el comando Select[lista,criterio]. Hay que definir previamente el criterio de selección el cual debe ser una función boleana. Para definir esa función es obligado quen uses la asignación diferida porque de no hacerlo Mathematica trataría de evaluar inmediatamente la expresión de la derecha lo que daría lugar a un error. Aquí tienes algunos ejemplos.

mayorque7[x_] := x > 7Select[Range[-2, 10], mayorque7]

multiplodetres[x_] := IntegerQ[x/3]   Select[Range[-15, 15], multiplodetres]

A veces podemos necesitar un criterio de selección que depende de un parámetro.  Por ejemplo, supón que queremos seleccionar  en una lista los números x con la propiedad de que k ≤ x < k + 1 y que eso queremos hacerlo para valores de k = 0, 1, 2,  ... , 9.  Podríamos definir diez criterios de selección de la forma "comprendidoentre2y3[x_] := 2 ≤ x < 3" y aplicar dichos criterios de uno en uno para seleccionar los elementos de la lista comprendidos en cada uno de esos intervalos. Pero es mejor hacerlo como sigue.

\(milista = Table[Random[Real, {0, 10}], {50}]\ ;  (*\ 
      generamos\ una\ lista\ con\ 50\ números\ aleatorios\ reales\ comprendidos\ entre\ 0\ y\ 10\ *) 
\(comprendidoentre[k_]\)[x_] := 
      k ≤ 
        x < \(\(k\)\(+\)\(1\)\(\ \)\( (*\ 
            así\ definimos\ de\ golpe\ todos\ los\ criterios\ de\ selección, \ 
            pues\ la\ función\ "comprendidoentre[k]"\ elige\ los\ números\ comprendidos\ entre\ k\ y\ k + 1\ *) \)\)\)
Table[Select[milista, comprendidoentre[k]], {k, 0, 9}]
Clear[milista, comprendidoentre]

Las funciones boleanas OddQ, EvenQ, IntegerQ, NumberQ, VectorQ, PrimeQ, NumericQ, Positive, Negative pueden usarse como criterios de selección.

Select[Table[3*k, {k, 15}], EvenQ]   Select[Table[3*k, {k, 15}], OddQ]   Select[{1, {2, 3}, a, {c}, d, e}, VectorQ]   
      Select[Sin[Range[2. , 5. ,  .25]], Positive]Select[Sin[Range[2. , 5. ,  .25]], Negative]

Los comandos Append[lista,elemento] y Prepend[lista,elemento] agregan "elemento" al final o al principio de "lista".

Append[{a, b, c}, 1]Append[{a, b, c}, {1, 2}]Prepend[{a, b, c}, 1]Prepend[{a, b, c}, {1, 2}]

Para extraer o eliminar elementos de una lista se usan Take[lista,{numero1,numero2}] y Drop[lista,{numero1,numero2}].

Take[{a, b, c, d, e, f}, {2, 4}]   Take[{a, b, c, d, e, f}, 3]   Take[{a, b, c, d, e, f}, -3]

Drop[{a, b, c, d, e, f}, {2, 4}]   Drop[{a, b, c, d, e, f}, 3]   Drop[{a, b, c, d, e, f}, -3]

El comando Partition[lista,k] hace una partición de "lista" en sublistas disjuntas de k elementos consecutivos. Si el número de elementos de "lista" no es múltiplo de k los últimos elementos se eliminan. Partition[lista,k,j] hace una lista con sublistas de k elementos consecutivos que empiezan en los elementos de "lista" que ocupan los lugares 1, j+1, 2j+1,...

Partition[{a, b, c, d, e, f, g}, 2]Partition[{a, b, c, d, e, f, g}, 3, 2]Partition[{a, b, c, d, e, f, g}, 4, 1]

Un comando que es muy útil para trabajar con listas es el comando Flatten[ ] que convierte una lista de listas en una lista.

Flatten[{{1, 2, 3}, {4, 5, 6}, {{7, 8}, {9, 10}}}]

Con mucha frecuencia hay que aplicar una función de varias variables a un vector o a los elementos de una lista cuyos elementos son vectores con igual número de elementos que variables tiene la función. La dificultad está en que las funciones en Mathematica conviene definirlas de la forma func[x,y] o func[x,y,z] y así definidas no pueden trabajar directamente sobre vectores de la forma {x,y} o {x,y,z}. No es lo mismo f[1,2] que f[{1,2}]. Observa la (importante) diferencia.

f[x_, y_] = x + (y^2);
{f[{1, 2}], f[1, 2]}

El comando Apply[func,expr] es útil para esto: reemplaza la cabeza de "expr" (Head[expr]) por "func". Este comando también se escribe en la forma f@@expr.

Clear[f]

    Apply[f, {a, b}]

    {Head[{a, b}], Head[f[a, b]]}

En particular, la función "func" en Apply[func,expr] puede ser, como hemos visto antes, "Plus", o "Times". Pero también puede ser cualquier función que definamos nosotros.

Clear[f]
f[x_, y_, z_] := {g[x, y, z], 
          h[x, y, z]};
Apply[f, {a, b, c}]
Clear[f, g, h]

Cuando queremos que nuestra función actúe sobre los elementos de una lista hay que especificar el "nivel" que dichos elementos ocupan en la lista. Apply[func,expr] actúa sobre el nivel 0 de "expr" que es "expr" (es decir, actúa sobre Head[expr]), esta es la opción por defecto; mientras que Apply[func,expr,posicion] actúa sobre los elementos de "expr" que ocupan la posición indicada (es decir, actúa sobre las cabeceras, Head[ ], de dichos elementos).

Apply[f, {{a, b}, {c, d}, {u, v, w}}] 

      Apply[f, {{a, b}, {c, d}, {u, v, w}}, {1}](* se aplica f en el nivel 1 de la lista, es decir, a los elementos de la lista *)

Para saber los elementos que ocupan un determinado nivel en una expresión puedes usar el comando Level[expr, nivel] cuya salida es una lista con los elementos de "expr" que ocupan el nivel indicado.

Level[{{a, b}, {c, d}, {u, v, w}}, {0}]  
Level[{{a, b}, {c, d}, {u, v, w}}, {1}]  
Level[{{a, b}, {c, d}, {u, v, w}}, {2}]

Como puedes ver, para Mathematica no es lo mismo "expr" que {"expr"} que {{"expr"}}.

Observa las diferencias entre Apply[func,expr] y Map[func, expr].

{Map[f, {a, b, c}], Apply[f, {a, b, c}]}
{Map[f, {{a, b}, {c, d}}], Apply[f, {{a, b}, {c, d}}, {1}]}

Hay otras formas de hacer esto mismo. Por ejemplo, usando una regla de sustitución (ver la siguiente sección).

f[x_, y_] = x^2 + 3  y;
lista = {{1, 2}, {3, 4}, {5, 6}, {7, 
            8}};
lista /. {x_?NumericQ, y_?NumericQ} -> 
        f[x, y]

Pero lo más cómodo es usar Apply[ ].

Apply[f, lista, {1}]

Mathematica dispone de muchísimos comandos para trabajar con listas. Uno de los más útiles con el que acabamos esta sección es el comando Outer[f, lista1, lista2] donde f puede ser una función o un comando.

Clear[f]
Outer[f, {a, b, c}, {1, 2, 3, 4}]

Outer[List, {a, b, c}, {u, v, x}]

Transpose[%]

Reglas de sustitución y reglas de sustitución diferidas. Operador de sustitución

Cuando queremos sustituir en una expresión un símbolo por un valor o por otro símbolo puede no ser conveniente usar el comando de asignación (=) porque dicho comando atribuye un valor fijo "para siempre". El siguiente ejemplo explica lo que quiero decirte.

polixy = x*y^2 + 3  x + 4  y

Podemos querer sustituir en dicha expresión la variable "x" por 7. Una forma de hacerlo es como sigue.

\(\(\(\(x = 7\)
polixy\)\(\ \ \ \)\( (*\ 
      polixy\ ha\ cambiado\ *) \)\)\)

El inconveniente de esto es que ya no puedo seguir usando "x" como una variable. Hay otra forma de proceder más cómoda que consiste en decirle a Mathematica "sustituye x por 7". Para ello necesitas DOS comandos: una regla de sustitución (x -> 7) y el comando de sustitución ( /. ). Una regla de sustitución inmediata es un comando de la forma "expr1 -> expr2" (que es otra manera de escribir Rule[expr1,expr2]) que representa una regla que transforma "expr1" en "expr2". Puedes escribir  ->  con mejor apariencia tecleando "Esc -> Esc". Este es el resultado ->. El operador de sustitución "expr /. reglas" (que es otra manera de escribir ReplaceAll[expr,reglas]) trata de aplicar una regla o varias reglas después de evaluar "expr". Por ejemplo "expr1 /. b->c" significa que en la expresión "expr1" después de ser evaluada todas las apariciones de b deben ser inmediatamente sustituidas por c. Observa su uso.

\(\(\(Clear[x]\ \ \ \  (*\ limpiamos\ el\ símbolo\ x\ *) 

      polixy\ \ \ \ \ \ \ \ \  (*\ 
        polixy\ vuelve\ a\ ser\ lo\ que\ era\ *) 
\(\(polixy /. 
        x -> 7\)\(\ \ \ \ \)\(
\)\)		 (*\ 
        atribuimos\ a\ x\ el\ valor\ 7\ obteniendo\ igual\ resultado\ que\ antes\ *) 
polixy\)\(\ \ \ \ \ \ \ \ \ \)\( (*\ 
      polixy\ no\ ha\ cambiado\ *) \)\)\)

?x

Cuando en un comando del tipo "expr /. regla" no puede aplicarse ninguna regla de sustitución Mathematica devuelve "expr" sin cambio ninguno.

a + 3  b /. c -> 2

2  a + 5  a /.  2  a -> 3 
(* primero se evalúa la expresión por lo que no puede aplicarse la regla *)

Es importante advertir que el operador de sustitución aplica las reglas de sustitución exactamente una sola vez en cada parte de la expresión que hay a su izquierda.

3  (x^2) (1 + x + (x^2)) /. {(x^2) -> x, 
        x -> a}

Si queremos que se apliquen las reglas de sustitución repetidamente hasta que la expresión de la izquierda permanezca invariante debemos usar el comando  "expr //. reglas" (que es otra manera de escribir ReplaceRepeated[expr,reglas]). Observa la diferencia.

3  (x^2) (1 + x + (x^2)) //. {(x^2) -> x, 
        x -> a}

Cuando uses este comando debes ser cuidadoso para no entrar en un bucle infinito cosa que ocurriría si, por ejemplo, tratas de ejecutar algo como  x //. x -> x + 1.

Si aplicamos una lista de listas de reglas de sustitución obtenemos como resultado una lista cuyos elementos se obtienen aplicando cada una de las reglas de sustitución a la expresión que hay a la izquierda.

3  (x^2) 
        Cos[1 + x + (x^2)] /. {{(x^2) -> x}, {x -> a}}

Recuerda que algunos comandos de resolución de ecuaciones como, por ejemplo, Solve[ecuación,variable] y NSolve[ecuación,variable] tienen como salida una lista cuyos elementos son listas de reglas de sustitución de la forma variable -> solución. Por tanto, al aplicar la salida de estos comandos a "variable" obtenemos una lista con las soluciones: "variable/.Solve[ecuación,variable]".

Puedes aplicar reglas de sustitución a "patrones" de forma parecida a como se definen funciones. Aquí tienes dos ejemplos.

f[a] + f[a + b] + f[b*c] (u + v) /. f[x_] -> x^2

\(\(\(logprod = Log[x_*y_] -> \ Log[x] + Log[y];  (*\ 
      esta\ regla\ no\ está\ implementada\ en\ Mathematica\ porque\ para\ números\ complejos\ no\ es\ cierta\ en\ general\ *) 
\(\(Log[
        a*b*c]\)\(/.\)\(\ \)\(logprod\)\(\ \)\( (*\ 
        aplica\ una\ sola\ vez\ el\ comando\ de\ sustitución\ *) \)\)

    Log[a*b*c] //. logprod\)\(\ \)\( (*\ 
      aplica\ repetidamente\ el\ comando\ de\ sustitución\ *) \)\)\)

Al igual que ocurre con la asignación diferida ( := ) también es posible dar reglas de sustitución diferidas "expr1 :> expr2" (que es otra manera de escribir RuleDelayed[expr1,expr2]). Puedes escribir  :>  con mejor apariencia si tecleas "Esc :> Esc". Este es el resultado :>. En el siguiente ejemplo puedes apreciar la diferencia entre usar unas u otras.

{x, x, x, x, x} /. x -> Random[]

{x, x, x, x, 
        x} /. x:> Random[]

Ten en ecuenta que cuando se ejecuta un comando de la forma "epr1 /. expr2[x_,y_,...]->expr3[x,y,...]" Mathematica primero evalúa "expr1", después evalúa la regla de sustitución "expr2[x_,y_,...]->expr3[x,y,...]" y finalmente realiza la sustitución indicada. Por eso cuando en la regla "expr2[x_,y_,...]->expr3[x,y,...]" se realizan operaciones simbólicas, por ejemplo x_/y_ ->Simplify[x /y], estas se ejecutarán antes de aplicar la regla lo que, en este caso al ser Simplify[x/y] = x/y (pues x e y son solamente símbolos y nada hay que simplificar) obtendríamos la regla x_/y_ ->x /y que no haría absolutamente nada. Estos casos se solucionan usando una regla de sustitución diferida de la forma x_/y_ :>Simplify[x/y].

(((x^2) - 1)/(x + 1)) /. x_/y_ -> Simplify[x/y]   
(((x^2) - 1)/(x + 1)) /. x_/
          y_:> Simplify[x/y]

Cálculo de extremos locales

Consideremos la siguiente función polinómica.

Clear["Global`*"]

f[x_] := 
      0.487857 x + 0.417177 (x^2) - 4.13604 (x^3) + 3.01854 (x^4) + 
        13.0446 (x^5) - 13.0802  (x^6) - 0.983014 (x^7) + 1.07625 (x^8) - 
        9.44444  (x^9) + 10  (x^10)

Los puntos críticos son los ceros de la derivada.

puntoscriticos = NSolve[f'[x] == 0, x]

Hemos obtenido una lista cuyos elementos son listas formadas por un único elemento que es una regla de sustitución de la forma x -> solución. Para obtener una lista con los puntos críticos basta aplicar a x la lista de listas de reglas de sustitución obtenida.

puntoscriticos = x /. puntoscriticos

Solamente nos interesan los puntos críticos reales. Podemos seleccionarlos fácilmente como sigue.

numeroreal[x_] := Im[x] == 0   puntoscriticos = Select[puntoscriticos, numeroreal]

Podemos obtener los máximos relativos como sigue.

maxrelativo[x_] := f''[x] < 0\(maximosrelativos = Select[puntoscriticos, maxrelativo]; 

    Print["Hay máximos relativos en los puntos
", 
      maximosrelativos]\)

Procedemos de igual forma para obtener los mínimos relativos.

minrelativo[x_] := f''[x] > 0\(minimosrelativos = Select[puntoscriticos, minrelativo]; 

    Print["Hay mínimos relativos en los puntos
", 
      minimosrelativos]\)

Variables locales y variables globales

Cada vez que declaras una variable o una función Mathematica la guarda en algún contexto. Por defecto el contexto en el que se guardan todas las variables y funciones definidas por el usuario es el contexto Global. Esto es así para que dichas variables y funciones puedan ser usadas en cualquier celda en cualquier momento.

\(var = 10; 
\(?var\)\)

Mathematica te dice que var es una variable almacenada en el contexto Global cuyo valor actual es 10. Muchas veces es conveniente usar alguna variable o alguna función para realizar una tarea muy específica y no nos interesa que dicha variable o función pueda ser accesible desde otras instancias del programa. Para ello Mathematica dispone del comando Module[{x,y,... },expr]que especifica que los símbolos x, y,... en expr deben ser tratados como variables locales.

Module[{var}, var = 0; var](* Mathematica nos devuelve el valor de la variable local "var" *)

var

Aquí tienes otro ejemplo para que entiendas la diferencia entre variable global y local.

\(t = 5; 
    \  (*\ declaramos\ "t"\ como\ variable\ global\ con\ valor\ inicial\ 5
        \ *) 
f[x_] := Module[{t}, t = \((1 + x)\)^2; Expand[t]] (*\ 
      declaramos "t" como\ una\ variable\ local\ *) \)f[a + b]

?t

Observa que Module[{x,y,...}, expr ] tiene dos argumentos: las variables locales, {x,y,...}, y "expr" que es una expresión o una sucesión de expresiones separadas por punto y coma. Recuerda que para que Mathematica realice una sucesión de operaciones y muestre en pantalla la última basta con que las escribas una a continuación de otra separadas por punto y coma.

La localización de variables combinada con el operador de sustitución es muy útil para definir derivadas de forma segura.

Clear[f, h]f[x_] := x^2 * Tan[x]h[x_] := Module[{s}, D[f[s], {s, 5}] /. s -> x]   h[3]Clear[f, h]

De igual forma se procede para definir derivadas parciales. Podemos definir fácilmente un comando que calcule el vector gradiente de una función de tres variables.

grad[f_][x_, y_, z_] := {D[f[x, y, z], x], D[f[x, y, z], y], 
        D[f[x, y, z], z]}

Observa que la función también debe ser una variable en nuestro comando porque queremos que nuestro comando valga para cualquier función y no solamente para una función concreta. La expresión "grad[f_][x_,y_,z_]" es otra forma de escribir "grad[f_,x_,y_,z_]". En Mathematica puedes agrupar las variables en corchetes como tú quieras. Veamos si nuestro comando funciona.

h[x_, y_, z_] := (z^2)*Sin[x*y]grad[h][x, y, z]

Todo parece correcto pero no lo es.

grad[h][0, 1, 1]Clear[h]

Nos ha vuelto a pasar lo mismo que antes. Aunque en este caso es posible usar la asignación directa (porque Mathematica sabe que " f " es una variable y realiza simbólicamente las operaciones indicadas sobre " f ") lo más prudente es localizar las variables respecto a las que derivamos y, una vez hecha la derivación, sustituimos las variables locales por las nuestras.

Clear[grad]grad[f_][x_, y_, z_] := 
      Module[{u, v, 
          w}, {D[f[u, v, w], u], D[f[u, v, w], v], D[f[u, v, w], w]} /. {
            u -> x, v -> y, w -> z}]

h[x_, y_, z_] := (z^2)*Sin[x*y]grad[h][x, y, z]grad[h][0, 1, 1]

Veamos otro ejemplo del uso de variables locales. Supongamos que queremos obtener valores de una función que depende de un parámetro que toma valores de forma aleatoria.

Clear[f];
a := Random[Integer, {1, 5}] (* 
      a toma valores enteros comprendidos entre 1 y 5 de forma aleatoria .  
          Observa que no podemos usar la asignación directa *) 
f[x_] := a*x^2 + 7  x - a ;   
Table[f[x], {x, 1, 5}] // Sort   
Table[a*x^2 + 7  x - a, {a, 1, 5}, {x, 1, 5}] // 
        Flatten//Union

Salvo rara casualidad, podrás ver que en la primera lista hay valores que no están en la segunda. Extraño, ¿verdad? Para saber lo que está pasando podemos pedir a Mathematica que ejecute paso a paso el comando conflictivo.

Trace[f[2]]

Procura entender lo anterior. Ahora está claro lo que pasa: para evaluar la función Mathematica ha llamado dos veces a la variable "a" asignando valores distintos en cada caso lo que provoca el error. Esto puede arreglarse localizando el parámetro.

Clear[f];
valores := 
        Module[{b}, a := Random[Integer, {1, 5}]; b = a; 
          f[x_] := b*x^2 + 7  x - b; 
          Table[f[x], {x, 1, 5}] // Sort ];
valores

Programación basada en listas

Mathematica proporciona la posibilidad de programar usando listas. Con frecuencia es posible sustituir procesos iterativos y bucles por el uso apropiado de listas. Supongamos, por ejemplo, que queremos programar el cálculo de los puntos medios de una lista de números; es decir, se trata de calcular los puntos medios de cada dos números consecutivos de la lista dada. Hagamos esto de dos formas diferentes. En la primera usamos programación procedimental y en la segunda programación basada en listas.

\(puntosmedios[lista_] := 
      Module[{x},  (*\ localizamos\ la\ variable\ "x"\ *) \ 
			
        x = Table[0, {Length[lista] - 1}]\  (*\ valor\ inicial\ de\ x\ *) ; 

        		For[k = 1, k ≤ \ Length[lista] - 1, 
          x[\([k]\)] = \((lista[\([k]\)] + lista[\([k + 1]\)])\)*0.5; 
          \(k++\)] (*\ calculamos\ los\ puntos\ medios\ *) \ ; 
		x
        \  (*\ pedimos\ a\ Mathematica\ que\ devuelva\ el\ valor\ actual\ de\ 
            x\ *) ]\)

puntosmedios[Range[0, 100]]

nuevospuntosmedios[lista_] := 
      (Drop[lista, 1] + Drop[lista, -1])*0.5

nuevospuntosmedios[Range[0, 100]]

Pidamos a Mathematica que nos diga el tiempo que tarda en calcular con los comandos que acabamos de definir los puntos medios de una lista con los primeros cien mil números.

Timing[puntosmedios[Range[0, 100000]]][[1]]

Timing[nuevospuntosmedios[Range[0, 100000]]][[1]]

Como puedes comprobar la segunda forma es mucho más eficaz que la primera.

Compresión de una lista de datos

Vamos a ver con un ejemplo que, en Mathematica, el uso de comandos específicos para trabajar con listas suele dar mejores resultados que la "programación al estilo C". 
Se trata de comprimir una lista de datos por el procedimiento de sustituir cada cadena de m repeticiones de un mismo dato "x" por {x,m}. Consideremos, por ejemplo, la lista siguiente.

lista = {2, 1, 1, 1, 3, 3, 5, 5, 5, 5, 1, 1, 3, 3, 3, 3, 7}

La salida del comando que queremos definir debe ser {{2,1},{1,3},{3,2},{5,4},{1,2},{3,4},{7,1}}. Podemos usar el comando Split[lista] que divide una lista en sublistas formadas por las cadenas de un mismo elemento repetido en la lista inicial.

temp = Split[lista]

Todo lo que tenemos que hacer ahora es contar el número de elementos de cada una de las sublistas y presentar el resultado de la forma deseada. Para ello definimos una función apropiada y la aplicamos a la lista anterior.

codi[x_] := {x[[1]], Length[x]};
Map[codi, temp]

Podemos resumir todo el proceso como sigue.

Clear["@"]
comprime[x_] := 
      Module[{temp, codi}, temp = Split[x]; 
        codi[z_List] := {z[[1]], Length[z]}; 
Map[codi, 
          temp]]

Para comprobar la eficacia del comando definido vamos a hacer que codifique una lista muy larga. El siguiente comando "listaaleatoria[n_,prob_]" genera una lista de n elementos cada uno de ellos elegido al azar del 1 al 100 y la probabilidad de que un elemento elegido se repita en la siguiente elección es igual a "prob".

listaaleatoria[n_, prob_] := 
      Module[{c}, c = Random[Integer, {1, 100}]; 
        Table[If[Random[] > prob, c = Random[Integer, {1, 100}], 
            c], {n}]]

listaaleatoria[300,  .7]

d = listaaleatoria[100000,  .7];
Timing[comprime[d];]

Hemos usado comandos específicos de Mathematica para trabajar con listas (Split[ ]). Podemos hacer esto mismo con programación procedimental.

nuevocomprime[x_] := 
      Module[{lista, resultado, count, actual}, lista = x; resultado = {}; 
        While[lista ≠ {}, actual = First[lista]; count = 0; 
          While[lista ≠ {} && 
              actual == First[lista], ++count; 
            lista = Rest[lista];]; AppendTo[resultado, {actual, count}];]; 
        resultado]

Probemos este comando.

tp = listaaleatoria[50,  .7]
nuevocomprime[tp]

Comparamos los dos comandos definidos.

d = listaaleatoria[10000,  .7];
Timing[comprime[d];]
Timing[nuevocomprime[d];]

La cosa está clara. Naturalmente, hay ocasiones en las que una combinación de estos procedimientos es lo adecuado.

criba[
        n_Integer]:=Module[{lista = Range[n], 
          p}, 
For[p = 2, 
          p ≠ 1 && 
            p ≤ 
              Floor[Sqrt[
                  n]], 
p++, 
Do[
            lista[[i]] = 1, {i, 2  p, n, 
              p}]]; 
DeleteCases[lista, 1]]

Este comando es bastante rápido.

Timing[criba[100000];]

Programación funcional

Mathematica proporciona un entorno de programación funcional cuyos comandos principales son los siguientes:
Nest[f,expr,n] aplica n veces la función f a expr. 
NestList[f,expr,n] proporciona una lista de n + 1 elementos cuyos elementos son las iteradas sucesivas de f sobre expr, empezando en el valor inicial expr y terminando en (f^([n]))(expr) (el resultado de aplicar f n veces a expr). 
WhileNestList[f, expr, test] proporciona la lista de las iteradas sucesivas de f hasta que "test" da False. Se entiende que "test" es una función booleana que actúa sobre las salidas de las iteradas de f .
En los comandos anteriores "expr" puede ser una lista. En cuyo caso el comando calcula las sucesivas listas aplicando a cada elemento de "expr" la función f. La salida es ahora una lista de listas.
FoldList[f, x, {a,b,c,d,...}]proporciona la lista {x, f[x, a], 
        f[f[x, a], b],  ... } .
Fold[f, x, {a,b,c,d,...}]proporciona el último elemento de FoldList[f, x, {a,b,c,d,...}].
Los siguientes ejemplos muestran la potencia de estos comandos.

maxima[x_]:= Union[FoldList[Max, First[x], x]] (* esta función selecciona en una lista numérica los elementos que son mayores que todos los que les preceden *)
lista = {47,43,43,43,43,43,43,43,43,171,171,171,171,171,171,171,171,88,88,88,23,23,23,23,23,23,23,23,90,90,237,237,237,237,53,53,116,140,140,140,140,85,85,85,85,85,85,85,85,85,85,85,85,85,241,241,61,61,171,171,171,171,171,196,196,196,196,196,196,196,196,196,5,5,5,250,250,250,245,245,245,66,56,56,56,56,56,56,56,190,190,190,190,190,34,34,34,34,34,34};
cumbres = maxima[lista]

Si quieres saber los lugares que ocupan los números anteriores la primera vez que aparecen en la lista, puedes hacerlo como sigue.

Table[{cumbres[[k]], Position[lista, cumbres[[k]]][[1, 
            1]]}, {k, 1, 5}]

Clear["@"]

g[z_] := I^z      
lista = NestList[g, 0.  + I, 39] (* los primeros 40 términos de la sucesión x[n + 1]=(I^x)[n]con x[1] = I *)
Show[
      Graphics[{PointSize[ .015], 
          Map[Point, Transpose[{Re[lista], Im[lista]}]]}], Axes -> True, 
      PlotRange -> All, AspectRatio -> 1];

Veamos una función que extrae 5 cartas al azar de una baraja de 40 cartas.

cartas = 
      Flatten[Outer[List, 
          Range[10], {♣, ♠, ◆, ♡}], 1] (* nuestra baraja *) 
extrae := 
      Module[{elige}, 
        elige[lista_] := 
          Drop[lista, {Random[
                Integer, {1, Length[lista]}]}]; 
Complement[
          cartas, Nest[elige, cartas, 5]]]

Primitivas gráficas

El comando básico para representar gráficas en el plano es Plot[funcion,{x,xmin,xmax},opciones]. Ya debes conocer dicho comando y sus opciones principales. Hagamos una representación gráfica muy sencilla.

Plot[x^2, {x, -1, 1}]

Como puedes ver, el comando Plot[ ] representa la gráfica y proporciona la salida "Out[n]=- Graphics - ". Seguro que te has preguntado muchas veces qué quiere decir ese “-Graphics-”. Pues bien, ahí guarda Mathematica toda la información sobre el gráfico en cuestión. Podemos verla con el comando InputForm[ ].

InputForm[%]

Este resultado muestra que un gráfico es un comando de la forma

Graphics[primitivas gráficas,opciones].

En este caso todas las opciones son las prefijadas pues no hemos especificado ninguna y hay una única primitiva gráfica, Line[ ], cuya sintaxis es

Line[{{x1,y1},{x2,y2}...}]

que dibuja una línea poligonal que une los puntos {x1,y1},{x2,y2}...; es decir, el gráfico generado por Plot[ ] es una poligonal formada por segmentos que unen puntos próximos de la gráfica de la función. Estos puntos son tan próximos que, a simple vista, no se advierte que la curva está formada por la yuxtaposición de los segmentos que unen cada punto con el siguiente. 
Además de Line[ ] otras primitivas gráficas son Point[ ], Rectangle[ ], Circle[ ], Disk[ ], Polygon[ ] y Text[ ]. La primitiva Polygon[{x1,y1},{x2,y2},..] representa un polígono relleno cuyos vértices son los puntos {x1,y1},{x2,y2}, . . . y cuyos lados son los segmentos que unen cada punto con el siguiente y el último con el primero. Por ejemplo, Polygon[{0,0},{1,0},{0,1}] es un triángulo. Un caso especial de Polygon[ ] es Rectangle[ ] en la que se dan los vértices inferior izquierdo y superior derecho. La primitiva Disk[ ] dibuja círculos rellenos. La primitiva Circle[{x,y},{rx,ry}], produce una elipse de semiejes rx y ry. Para obtener arcos de circunferencia o sectores de círculos o de elipses basta incluir al final del correspondiente comando una lista de la forma {t1,t2} donde 0≤t1<t2≤2Pi. Por ejemplo, Disk[{1,1},{2,3},{Pi/2,3Pi/2}] es un sector de elipse comprendido en el semiplano de la izquierda. Si tienes alguna duda sobre la sintaxis de estas primitivas gráficas debes consultar la ayuda de Mathematica. 
Como su nombre indica, las primitivas gráficas son los elementos básicos (primitivos) que utiliza Mathematica para crear gráficos complejos. El siguiente ejemplo muestra a la primitiva Point[ ] en acción. Usamos para ello el comando ListPlot[ ] que sirve para representar gráficamente listas de puntos en el plano.

\(ListPlot[Table[i^2, {i, 3}], 

        PlotStyle -> {PointSize[ .02], RGBColor[1, 0, 0]}]\)InputForm[%]

Observa que los puntos de un gráfico realizado con ListPlot[ ] son producidos por la primitiva gráfica Point[ ]. Fíjate también en que las especificaciones de color y tamaño de los puntos, que indicamos con PlotStyle figuran en una lista que contiene como sublista a la lista de los puntos del gráfico a la cual preceden. Las especificaciones como PointSize[ ] o RGBColor[ ] que preceden a una primitiva gráfica o a una lista de primitivas gráficas a las que modifican, se conocen como directivas gráficas o, simplemente, directivas. Otras directivas que ya conoces son Thickness[ ] y Dashing[ ]. En general, las especificaciones dadas con PlotStyle en comandos como Plot[ ],  ListPlot[ ], o ParametricPlot[ ] que tienen como salida un objeto gráfico, son directivas gráficas que se aplican globalmente. Lo que hacen precisamente estos comandos es facilitar la construcción de objetos gráficos, es decir de comandos del tipo Graphics[primitivas gráficas,opciones] y mostrarlos mediante un comando Show[ ]. 
Puedes construir directamente objetos gráficos en Mathematica dando una lista (de listas) de primitivas gráficas. Es posible incluir en cada lista directivas que especifican la forma en que se representarán los siguientes elementos gráficos de esa lista o de sus sublistas, pero dichas directivas no tiene ningún efecto fuera de la lista que las contiene. Procura entender los ejemplos que siguen.

\(vertices = {Hue[0] (*\ especificamos\ color\ *) , 
            PointSize[ .03]\  (*\ 
              especificamos\ tamaño\ de\ cada\ punto\ *) , 

            Table[Point[{Cos[2\ k\ Pi/5], Sin[2\ k\ Pi/5]}], {k, 0, 5}]}\);   
\(lados = {Hue[ .5] (*\ especificamos\ color\ *) , 
            Thickness[ .02] (*\ 
              especificamos\ grosor\ de\ línea\ *) , 
            Line[Table[
{Cos[2\ k\ Pi/5], Sin[2\ k\ Pi/5]}, {k, 0, 5}]]}\);   
pentagono = 
          Show[Graphics[{lados, vertices}], AspectRatio -> Automatic];

\(Show[Graphics[{{Hue[0], Rectangle[{0, 0}, {1, 1}]}, 
            
{Hue[ .5], Thickness[ .02], Circle[{1, 1}, 1/3]}}], 

        AspectRatio -> Automatic]; \)

Observa que en estas representaciones no figuran los ejes de coordenadas. Ello se debe a las opciones por defecto del comando Graphics[ ] entre las que figura Axes->False.

Options[Graphics]

Show[Graphics[Circle[{0, 0}, {2, 1}]], AspectRatio -> Automatic, 
      Axes -> True];

Para hacer la representación gráfica de una función, el comando Plot[ ] llama muchas veces a dicha función. Podemos verlo con el siguiente ejemplo.

Clear[f, n];
n = 0; 
    f[x_] := (n++; Sin[(x^3)])   
Plot[f[x], {x, 0, Pi}];
n

Por eso, cuando la función es complicada, el comando Plot[ ] puede consumir mucho tiempo. Algunas veces puede ser conveniente pedirle a Plot[ ] que evalúe la función, lo que se hace con Plot[Evaluate[f[x]],{x,a,b}].

Las opciones Prolog y Epilog

Las opciones Prolog->{primitivas gráficas} y Epilog->{primitivas gráficas} sirven para añadir a una representación gráfica dada por un comando como Plot[ ] o ParametricPlot[ ] elementos gráficos especificados por las correspondientes primitivas gráficas. La diferencia entre ellos es que Prolog añade los elementos gráficos antes de que se represente la parte principal de la gráfica y Epilog los añade después. Los siguientes ejemplos ponen de manifiesto este comportamiento.

Plot[Sin[x], {x, -Pi, Pi}, 
      PlotStyle -> {Hue[ .64], Thickness[ .02]}, 
      Epilog -> {PointSize[0.05], 
          Hue[0], {Point[{π/7, Sin[π/7]}], 
            Point[{π/5, Sin[π/5]}], 
            Point[{π/3, Sin[π/3]}]}}];

Plot[Sin[x], {x, -Pi, Pi}, 
      PlotStyle -> {Hue[ .64], Thickness[ .02]}, 
      Prolog -> {PointSize[0.05], 
          Hue[0], {Point[{π/7, Sin[π/7]}], 
            Point[{π/5, Sin[π/5]}], 
            Point[{π/3, Sin[π/3]}]}}];

Funciones condicionadas

En esta sección vamos a ver cómo se pueden definir funciones a trozos y cómo podemos imponer que una función determinada trabaje solamente con cierto tipo de datos. El operador Condition  ( /; ) tiene distintos usos; el que nos interesa ahora es de la forma "expr1:=expr2 /; test" que representa una definición que debe aplicarse solamente si "test" da "True". Aquí es indispensable usar la asignación diferida porque en otro caso Mathematica trata de comprobar inmediatamente "test" con valores simbólicos lo que produce un error. Aquí tienes un ejemplo.

Clear[f]f[x_] := 2 - x  /; x < 0f[x_] := x^2 /; 0 ≤ x < 1f[x_] := 2 + x /; 1 ≤ xf[-2]f[0]f[1]

Otro ejemplo.

Clear[f]f[x_, y_] := Log[x + y] /; 0 < x + y  &&  x^2 + y^2 < 16f[-3, 2]   f[2, 1]f[3, 2]f[3, 3]

Mathematica trabaja con varios tipos de números como, por ejemplo, enteros (Integer), reales (Real) y complejos (Complex). Debes saber que, salvo que se indique otra cosa, Mathematica trata por defecto todas las variables como números complejos y esa es la razón de que a veces sus respuestas puedan parecerte erróneas o extrañas. Por ejemplo, para los números complejos no siempre es cierto que (Sqrt[a])(Sqrt[b]) sea igual a (Sqrt[a b]) ni tampoco es cierto en general que Log[Exp[z]] sea igual a z. Puedes decir a Mathematica el tipo de variable con el que quieres que trabaje una función de la forma que sigue.

Clear[f]f[x_Integer] := x^3    f[-2]f[1/2]f[-2. ]

También es posible usar el operador de condición con los argumentos como en el siguiente ejemplo.

g[x_ /; EvenQ[x], y_ /; OddQ[y]] := x*y 
    
  g[2, 3]g[3, 2]Clear[g]

Otra forma de hacer esto mismo es la siguiente.

g[x_?EvenQ, y_?OddQ] := x*y 
(* x_?EvenQ comprueba si x es un número par .  El símbolo _ es un comodín que representa calquier expresión; ?EvenQ restringe dichas expresiones a las que dan  
              "True" cuando se aplica el test 
              EvenQ .  Análogamente y_?OddQ comprueba si y es 
              un número impar *)g[2, 3]g[3, 2]Clear[g]

Esta forma de proceder se aplica también para las reglas de sustitución como muestra el siguiente ejemplo.

{7, -9, 2.4, (3/5), π} /. 
        x_?IntegerQ -> (x^2)
(* aplica la regla de elevar al cuadrado los elementos de la lista de la izquierda que son números enteros *)

Expresiones

En Mathematica todo son expresiones. Una expresión está formada por la cabeza (Head) de la expresión y otros argumentos que a su vez pueden ser expresiones. Para entender lo que significa esto vamos a ver la forma en que Mathematica representa internamente algunos objetos familiares. Para eso usamos el comando "FullForm[expr]". Procura entender lo que sigue.

FullForm[a + b]Head[a + b]

FullForm[a*b*c]Head[a*b*c]

FullForm[a^b]Head[a^b]

Head[2]Head[2. ]Head[1/2]

FullForm[a -> b]FullForm[a :> b]

FullForm[_]FullForm[x_]FullForm[x_Real]

Ahora puedes entender mejor lo de los patrones o modelos. Por ejemplo, cuando aplicas la siguiente regla de sustitución:

regla = a_^b_ -> a*b

a una expresión como la siguiente

(Sqrt[5]) + (π^3) + 1/π + x*y /. regla

lo que estás haciendo es reemplazar por a*b todas las partes de la expresión de la izquierda que son de la forma:

FullForm[a^b]

Comprueba:

FullForm[(Sqrt[5])] (* es de la forma Power[a, b] *)FullForm[(π^3)] (* es de la forma Power[a, b] *)FullForm[1/π] (* es de la forma Power[a, b] *)FullForm[x*y] (* no es de la forma Power[a, b] *)

Sobre el comando Print[ ]

El comando Print["cadena de texto"] debe usarse muy poco en Mathematica. No olvides que Mathematica saca todo en pantalla por lo que solamente hay que peocuparse de lo que NO queremos que muestre en pantalla (poniendo un ; al final) porque lo consideramos una información innecesaria. Pero hay que evitar usar el comando Print[ ] para pedirle a Mathematica que muestre en pantalla lo que el programa ya muestra por sí solo. Un inconveniente de Print[ ] es que su salida es considerada siempre como una cadena de texto y no puede ser utilizada en ningún tipo de cálculo numérico.

a = Sin[1.5]  
b = Print[Sin[1.5]]   
Head[a]
Head[b]
a + a   
a+b

Sobre la evaluación de funciones

Las funciones deben definirse en la forma f[x,y] o f[x,y,z], es decir deben incluir todas las variables en un corchete, pues de esta forma se pueden evaluar directamente. Para evaluar una función, f[x,y] correctamente definida, no hay que atribuir valores a las variables porque es un trabajo innecesario e inconveniente. Si queremos evaluar f[x,y] en el punto (1,2) basta con escribir f[1,2] y Mathematica mostrará de forma automática, sin necesidad de que nosotros lo pidamos, el resultado en pantalla.

poli = (x^2)*y + (y^3)*z + x*y*z ;  
mismopoli[x_, y_, z_] = 
        (x^2)*y + (y^3)*z + x*y*z ;
  
poli /. {x -> 1, y -> 2, z -> 3}   
mismopoli[1, 2, 3]

Cuando definas un comando que deba trabajar sobre funciones has de tener en cuenta el tipo de funciones con las que va a trabajar y definirlo de forma adecuada. En el siguiente ejemplo se define un comando que calcula la divergencia de un campo escalar de tres variables.

Clear[divergencia]

divergencia[func_][x_, y_, z_] := 
        Module[{u, v, 
            w}, (D[func[u, v, w], u] + D[func[u, v, w], v] + 
                D[func[u, v, w], w]) /. {u -> x, v -> y, 
              w -> z}];
g[x_, y_, z_] = x Sin[x y z];
divergencia[g][x, y, z]
divergencia[g][1, Pi/2, 1]

Al definir el comando yo sé que le voy a pasar una función de tres variables y que la expresión "func[u,v,w]" que se ha usado al definirlo tendrá sentido. El comando no funcionará si "func" no está definida de la forma apropiada. Observa la diferencia.

h[{x_, y_, z_}] = x Sin[x y z];   
divergencia[h][x, y, z]   
D[h[x, y, z], x]   
D[h[{x, y, z}], x]

Si quisiéramos que nuestro comando inicial también trabajara sobre funciones que están definidas de la forma h[{x, y, z}]  =.  ..  , podemos añadir a nuestra definición anterior la siguiente. Fíjate que no hace falta cambiar el nombre del comando.

divergencia[{func_}][x_, y_, z_] := 
        Module[{u, v, 
            w}, (D[func[{u, v, w}], u] + D[func[{u, v, w}], v] + 
                D[func[{u, v, w}], w]) /. {u -> x, v -> y, 
              w -> z}];
divergencia[{h}][x, y, z]

Una mejor forma de proceder consiste en usar una función auxiliar que transforma una función de la forma h[{x,y,z}] en otra de la forma h[x,y,z].

func[{u, v, w}] /. List -> Sequence   
func[u, v, 
        w] /. List->Sequence

Clear[divergencia]
divergencia[func_][x_, y_, z_] := 
        Module[{u, v, w, H}, 
          H[u_, v_, w_] = 
            func[{u, v, w}] /. 
              List -> Sequence; (D[H[u, v, w], u] + D[H[u, v, w], v] + 
                D[H[u, v, w], w]) /. {u -> x, v -> y, 
              w -> z}];

Comprobemos si funciona.

divergencia[g][x, y, z]
divergencia[h][x, y, z] 
divergencia[g][1, Pi/2, 1]
divergencia[h][1, Pi/2, 1]

En resumen, cuando se define un comando que debe usar funciones hay que definir el comando teniendo en cuenta cómo se evalúa el tipo de funciones que se van a usar. Muchas veces el tipo de función depende de la forma en que queremos evaluarla. Por ejemplo, una función que pueda evaluarse en pares de puntos de la forma {{x,y},z} deberá definirse como func[{{x_, y_}, z_}] :=  ...  Cualquier comando al que se le pase dicha función deberá definirse teniendo en cuenta la forma en que se evalúa la misma.