Aggiornato per Mathematica versione 11

Funzioni: definizione e primi grafici

Gianluca Gorni
Università di Udine, Dipartimento di Matematica e Informatica

Ultima modifica: 18 gennaio 2018

Questo file è concepito principalmente per essere letto interattivamente su schermo. Per questo, e anche per risparmiare spazio, gli output di solito sono omessi. L'utente che legge da schermo li deve rigenerare mandando in esecuzione le celle di input una alla volta.

Funzioni definite con " = " (definizione immediata)

Con " = " (Set) si può assegnare un valore non solo a un simbolo, ma anche a una funzione:

Clear[x,f]

f[x_]=(x^2)

?f

Una volta caricata questa definizione possiamo calcolare il valore di f di qualsiasi argomento:

f[0]

f[5]

f[Sqrt[3]]

f[x+y]

Possiamo calcolare la derivata della funzione semplicemente con l'apostrofo:

f'[x]

f'[3]

f''[x]

f'''[x]

Mathematica distingue fra derivata di una funzione che ha un nome, e derivata di una formula. Quando ha per nome un simbolo (nel nostro caso il simbolo è f), si usa l'apostrofo dopo il simbolo:

f'[x]

Nel caso di derivata di una formula, si usa D e bisogna specificare la variabile rispetto a cui si deriva:

D[(x^3),x]

D[f[x],x]

Possiamo usare anche una notazione bidimensionale (∂_x)f[x] per la derivata di una formula:

usando la tavolozza  Palettes ▶ Basic Math Assistant ▶Typesetting (∂_□)□

usando l'abbreviazione da tastiera EscpdEsc (sta per partial derivative)

oppure ancora scrivendo D[f[x], x] e poi convertendo in formato standard col menu Cell ▶ Convert To ▶ StandardForm:

(∂_x)f[x]

La derivata si può fare anche di funzioni che non hanno nessuna definizione associata:

Clear[g,h];
D[g[h[x]],x]

La notazione con l'apostrofo è un'abbreviazione di una struttura un po' complicata, che usa Derivative:

FullForm[g'[x]]

D[g[x,y],x]

FullForm[D[g[x,y],x]]

Per calcolare una primitiva o un integrale definito possiamo usare Integrate:

Integrate[f[x],x]

Integrate[f[x],{x,a,b}]

Integrate[h'[x],x]

Integrate[h'[x],{x,0,1}]

DSolve[y'[x]==h[x],y[x],x]

Per l'integrazione si può usare anche la notazione di Leibniz,

usando la tavolozza  Palettes ▶ Basic Math Assistant ▶Typesetting ∫□d□ o  (∫_□^□)□d□

oppure le abbreviazioni da tastiera EscinttEsc e EscdinttEsc,

o ancora scrivere Integrate[f[x], x]o Integrate[f[x], {x,a,b}] e poi convertire in formato standard usando il menu Cell ▶ Convert To ▶ StandardForm:

∫f[x]\[DifferentialD]x

(∫_a^b)f[x]\[DifferentialD]x

(∫_0^3)f[x]\[DifferentialD]x

Notare che nella primitiva non viene aggiunta la costante d'integrazione.

Possiamo anche disegnare il grafico di una funzione, per esempio nell'intervallo fra -1 e 1:

Plot[f[x],{x,-1,1}]

Attenzione a non mettere le sottolineature dentro Plot:

Plot[f[x_],{x,-1,1}]

Per disegnare il grafico non era necessario avere definito la funzione. Bastava mettere la formula direttamente dentro Plot:

Plot[(x^3)-x,{x,-2,2}]

Analogamente si può mettere la formula della funzione dentro l'integrale:

(∫_0^1)((x^3)-x)\[DifferentialD]x

Più informazioni sui grafici di funzioni nel file numero 11.

Tornando a come definire una funzione, osservate la sottolineatura che segue il simbolo x nell'argomento di f. La sottolineatura compare solo a sinistra dell'uguale, non a destra. La sottolineatura fa interpretare l'espressione f[x_] come la "pattern" f calcolata su un qualsiasi cosa; quando il nòcciolo in séguito incontra una f avvolta attorno a una qualche espressione, assegnerà (temporaneamente) l'espressione alla variabile x, poi sostituirà questo valore nel secondo membro della definizione x^2.

f[x_]=(x^2)

f[Log[x+1]]

Se si tralascia la sottolineatura il secondo membro viene usato soltanto quando il nòcciolo incontra letteralmente " f[x] ", mentre lascierà intatte f[1], f[y] ecc.:

f[y]

Senza Pattern:

Clear[f];
f[x]=x^2

f[x]

f[1]

f[y]

f[2]

?f

Con Pattern:

Clear[f]

f[x_]=(x^2)

f[5]

f[x-2]

?f

Funzioni definite con " := " (definizione ritardata)

È molto più comune definire le funzioni con " := " ("definizione ritardata", SetDelayed) invece che con il semplice " = ":

g[x_]:=2(x^3)-x

g[0]

g[1]

1+g[x+y]

La differenza fra i due modi di definire è abbastanza sottile. Quando si definisce con " = " (definizione "immediata"), il secondo membro della definizione viene valutato all'istante, e alla pattern f[x_] viene associato il risultato della valutazione.

Invece, quando si usa " := "  (definizione "ritardata") il secondo membro viene memorizzato congelato alla lettera; quando il nòcciolo troverà g[qualcosa] prima sostituirà il qualcosa nel secondo membro ancora congelato, e solo dopo scongelerà e farà la valutazione.

Nella maggior parte dei casi il risultato dei due modi di definire è lo stesso. Tuttavia la definizione ritardata è raccomandata nella grande maggioranza dei casi. Il motivo è questo: supponiamo di aver fatto dei calcoli, durante i quali avevamo assegnato a x un valore, per esempio

x=3;

Più avanti ci siamo dimenticati di aver dato quest'assegnazione. Vogliamo definire una funzione usando la variabile x; e malauguratamente usiamo la definizione immediata:

h[x_]=(x^2)

Accidenti! Il secondo membro della definizione è stato valutato all'istante, col valore che la x per puro caso aveva in questo momento. Così ora la h vale sempre 9:

h[0]

h[2]

Per vedere che regole sono memorizzate con h basta valutare ?h:

?h

Se invece definiamo la h con la definizione ritardata

x=3

Clear[h];
h[x_]:=(x^2)

?h

h[5]

x

h[x]

La formula al secondo membro (x^2) non viene valutata subito, ma congelata in memoria. Da quel momento in poi ogni singola volta che h viene usato, nella formula congelata alla x viene sostituito il valore che si incontra dentro h[qualcosa], dopodiché si scongela e si calcola:

h[1]

h[2]

h[y]

?h

Proprio il pericolo di assegnazioni dimenticate di variabili fa raccomandare di usare la definizione ritardata (salvo controindicazioni in casi speciali).

Altro esempio: assegnamo 1+1 come valore di due simboli, uno con definizione immediata, l'altro con definizione ritardata:

a=1+1;
b:=1+1;

Con la definizione immediata Mathematica associa al simbolo il risultato dell'operazione 1+1:

?a

Con la definizione ritardata Mathematica associa al simbolo testualmente ciò che abbiamo scritto, senza eseguire calcoli:

?b

I calcoli vengono effettuati al momento in cui il simbolo viene usato in un calcolo:

b

Se in una formula la b compare più volte, lo scongelamento avviene altrettante volte, anche milioni di volte se càpita, e può sprecare diverso tempo. In tali casi potrebbe convenire una definizione immediata.

Colorazione sintattica

La colorazione sintattica dei simboli in Mathematica può aiutare a distinguere le definizioni immediate da quelle ritardate. Osservate il colore delle due x nella definizione immediata:

f[x_]=(x^2)

nel membro di sinistra la x è verde scuro (che segnala una variabile locale), mentre a destra è blu scuro (se il simbolo x non ha valori) oppure nero (se il simbolo x ha correntemente un valore). La x a destra è una variabile  globale, non locale.

Nella definizione ritardata

f[x_]:=(x^2)

entrambe le x sono verde scuro, perché sono in entrambi i casi una variabile locale, protetta contro assegnamenti esterni.

Numeri casuali e definizioni

Un caso in cui la distinzione fra definizioni ritardate e immediate risalta bene è con i numeri casuali. Il comando Random[] produce un numero in virgola mobile casuale fra 0 e 1:

RandomReal[]

Se assegnamo un numero casuale al simbolo c con definizione immediata

c=RandomReal[]

d'ora in poi, fino ad avviso contrario, il valore di c è fissato:

c

c

c==c

Se invece diamo una definizione ritardata

d:=RandomReal[]

ogniqualvolta il nòcciolo incontra d lo sostituisce con un valore casuale ricalcolato di fresco:

d

d

{d,d}

d==d

d-d

Ora, ci sono delle situazioni in cui è più appropriato il primo comportamento e altre in cui è meglio il secondo. Se per esempio vogliamo fare uno studio del singolo numero casuale sarà meglio la definizione immediata:

c>1/2

Sqrt[c]>.1

Se invece vogliamo per esempio fare una lista di numeri casuali useremo la definizione ritardata:

{d,d,d,d,d}

Una variabile che ogni volta che viene calcolata genera un intero casuale fra -4 e 4:

r:=RandomInteger[{-4,4}];

r

Un polinomio casuale con radici intere:

Clear[x]

Expand[(x-r)(x-r)]

Altro esempio di differenza fra immediato e ritardato

Supponiamo di voler definire f[x] come la derivata di (x^2) rispetto a x, ma facendo fare il calcolo a Mathematica. Partiamo con una definizione immediata:

x=3;
D[(x^2),x]

Clear[f,x];
f[x_]=D[(x^2),x];

?f

Questa f funziona normalmente:

f[y]

f[1]

f[-5]

Proviamo con una definizione ritardata:

Clear[g,x];
g[x_]:=D[(x^2),x];

Proviamo:

g[x]

g[y]

g[1]

g[-5]

Cosa succede????
La spiegazione è questa: quando si chiede g[1], Mathematica prende il secondo membro della definizione D[(x^2),x], e sostituisce x con 1, ottenendo D[(1^2),1]. Ora, la derivata di (1^2) rispetto a 1 non ha senso.

Nella definizione ritardata il secondo membro viene conservato intatto. Quando se ne chiede quanto vale per un dato valore di x, questo valore di x  viene prima sostituito nel secondo membro, e poi si esegue il calcolo.

Nella definizione immediata il secondo membro viene precalcolato subito fin dove ha senso. Quando se ne chiede quanto vale per un dato valore di x, questo valore di x  viene prima sostituito nel secondo membro già precalcolato, e poi si esegue il calcolo col nuovo valore.

E' una questione di ordine delle operazioni.

?f

?g

Morale: occasionalmente può succedere che la definizione immediata sia obbligatoria per avere l'effetto desiderato. Ma la cosa non càpita spesso a un principiante.

Clear[f,x];
f[x_]=Integrate[((E^(5x))/((E^x)-(E^(-2x)))),x]

f[5]

Clear[g,x];
g[x_]:=Integrate[((E^(5x))/((E^x)-(E^(-2x)))),{x,0,5}]

g[1]

Successioni ricorsive

Consideriamo la successione definita per ricorsione   {({{a[1]==1}, {a[n]==2a[n-1]}}). Per calcolarla con Mathematica basta scrivere

Clear[a]

a[1]=1;
a[n_]:=2a[n-1];

a[3]

a[15]

a[210]

(Notare la definizione ritardata nella seconda riga; la prima riga potrebbe essere anche ritardata, è indifferente). Con questo possiamo calcolare a[n] quando n è intero ≥1:

a[2]

a[3]

a[4]

a[12]

Attenzione: non si può definire a[n_+1]:=2a[n]. Infatti Mathematica applicherebbe questa regola soltanto quando dovesse calcolare il valore di a su un'espressione che sia esplicitamente della forma qualcosa+1. Mathematica non riscrive automaticamente qualcosa come (qualcosa-1)+1.

Altro esempio: il fattoriale (anche se è già predefinito):

fatt[0]=1;
fatt[n_]:=n*fatt[n-1];

fatt[12]

12!

b[0]=1;
b[n_+1]:=2b[n]

b[1]

La successione di Fibonacci (versione ingenua):

fib[0]=1;
fib[1]=1;
fib[n_]:=fib[n-1]+fib[n-2];

fib[5]

fib[15]

fib[16]

Un grafico ad albero dei calcoli ricorsivi da eseguire per i numeri di Fibonacci, nella forma ingenua:

FibonacciTree[k_Integer/;k≥2]:=
TreePlot[{{nuovo[k]}}//.{{nuovo[n_Integer/;n≥2]}:>Sequence@@{{n}->{n,nuovo[n-1]},{n}->{n,nuovo[n-2]}},HoldPattern[{a___}->{a___,nuovo[n_/;n≥2]}]:>Sequence@@{{a}->{a,n},{a,n}->{a,n,nuovo[n-1]},{a,n}->{a,n,nuovo[n-2]}}}/.nuovo->Identity,
Top,{k},VertexRenderingFunction->({Text[#2//Last,#1,Background->White]}&)]

FibonacciTree[8]

Si noterà che molti calcoli vengono ripetuti più di una volta. Questo è molto inefficiente per grandi valori di n. Vedremo più sotto come sveltire la procedura.

Notazione con gli indici in basso

Si può anche usare la più familiare notazione con gli indici in basso (Subscript, menu Insert > Typesetting > Subscript, tavolozza Palettes ▶BasicMathInput (Subscript[■, □]), vedi il tutorial Typing Subscripts):

(Subscript[b, 0])=1;
(Subscript[b, n_]):=2(Subscript[b, n-1])

(Subscript[b, 3])

(Subscript[b, 45])

C'è una complicazione però: quando si è definita una successione (o una funzione) usando Subscript, e poi la si vuole cancellare, il comando Clear non funziona:

Clear[b];

Clear[b];
(Subscript[b, 3])

(Subscript[b, 15])

Funziona invece l' =. (che è l'abbreviazione di Unset), oppure più drasticamente Remove:

(Subscript[b, n_])=.;
(Subscript[b, 3])

Remove[b];
(Subscript[b, 3])

Perché mai questo comportamento? Il motivo sta nell'organizzazione interna delle informazioni in Mathematica: le definizioni date tramite Subscript non vengono associate al simbolo b:

(Subscript[b, 0])=1;
(Subscript[b, n_]):=2(Subscript[b, n-1]);

?b

Vengono invece associate al simbolo Subscript:

??Subscript

C'è anche un modo (piuttosto arcano) di associare le definizioni al simbolo b (usando i cosiddetti UpValues):

Remove[b];
b/:(Subscript[b, 0])=1;
b/:(Subscript[b, n_]):=2(Subscript[b, n-1])

?b

In questo modo Clear cancella i valori di b:

Clear[b]

?b

Come far ricordare i valori calcolati

In una successione ricorsiva come   {({{a[1]=1}, {a[n]:=2a[n-1]}})

a[1]=1;
a[n_]:=2a[n-1];

a[4]

a[4]

a[2000]

per calcolare a[4] bisogna avere il valore di a[3]; per calcolare a[3] ci vuole a[2] eccetera. Supponiamo di avere già calcolato a[32]. Mathematica non si è annotata che a[32] ha quel valore. Quindi quando le chiedo di trovare a[33] si ricalcola daccapo a[1],a[2],... ,a[32], perdendo del tempo.
C'è un semplice trucco per far ricordare a Mathematica i valori già calcolati, per sveltire i conti successivi, ed è di fare una doppia assegnazione, una immediata all'interno di una ritardata:

Clear[a];
a[1]=1;
a[n_]:=a[n]=2a[n-1]

In questo modo quando viene chiesto il valore di a[3], oltre a venire calcolato il valore, questo viene assegnato all'espressione a[3], e usato nei conti successivi. Per esempio

a[3]

?a

a[7]

?a

Notate che la sottolineatura nella definizione a[n_]:=a[n]=2a[n-1] c'è soltanto al primo membro, non dopo.

Una versione furba della successione di Fibonacci che ricorda i valori già calcolati e non fa ripetizioni inutili:

Clear[fibFurba];
fibFurba[0]=1;
fibFurba[1]=1;
fibFurba[n_]:=fibFurba[n]=fibFurba[n-1]+fibFurba[n-2];

Con Timing possiamo vedere il tempo impiegato dal calcolo:

Timing[fib[20]]

Timing[fibFurba[20]]

Il comando RSolve (risolvere ricorrenze) funziona in modo simile al DSolve che risolve equazioni differenziali:

Clear[a]

RSolveValue[{a[n+2]==a[n]+a[n+1],a[0]==1,a[1]==1},a[n],n]

RSolveValue[{a[n+2]==a[n]+a[n+1],a[0]==1,a[1]==1},a[n],n]//FunctionExpand

Simplify[FunctionExpand[RSolveValue[{a[n+2]==a[n]+a[n+1],a[0]==1,a[1]==1},a[n],n]],Element[n,Integers]]

Il comando RecurrenceTable funziona con la sintassi simile a NDSolve dando valori numerici:

RecurrenceTable[{a[n+2]==a[n]+a[n+1],a[0]==1,a[1]==1},a[n],{n,0,10}]

Funzioni di più variabili

Si definiscono funzioni di più variabili dando a ognuna la sua sottolineatura:

f[x_,y_]:=(x^2)+(y^2)

f[0,1]

f[x+y,x-y]

Si possono calcolare le derivate (parziali) seconde, terze eccetera di una formula. Per esempio, la derivata di una formula rispetto a x e poi rispetto a y:

D[x (y^2),x,y]

La derivata rispetto a x e poi di nuovo rispetto a x:

D[x (y^2),x,x]

La derivata rispetto a y e poi di nuovo rispetto a y:

D[x (y^2),y,y]

La derivata rispetto a y due volte:

D[x (y^2),{y,2}]

La derivata rispetto a y tre volte:

D[x (y^2),{y,3}]

Il vettore delle derivate parziali rispetto a x e a y (cioè il gradiente):

D[x (y^2),{{x,y}}]

La matrice delle derivate parziali seconde rispetto a x e a y (cioè la matrice hessiana):

D[x (y^2),{{x,y},2}]
%//MatrixForm

Proviamo a calcolare una derivata parziale di una funzione che ha un nome ma non ha una formula associata:

Clear[f];
D[f[x,y],x]

Derivative)

Si vede che viene una notazione che imita quella della derivata nel caso di una variabile: f'[x].  L'esponente di f nell'espressione Derivative[1,0][f][x,y] significa che la f è derivata (Derivative) del simbolo f una volta rispetto alla prima variabile e 0 volte rispetto alla seconda. Altro esempio:

D[f[x,y],y]

D[f[x,y],x,y]

D[f[x,y],{x,2},y]

Mathematica può anche dare formule per le derivate di ordine simbolico:

D[(x^2),{x,n}]
D[(x^2),{x,n}]//FunctionExpand
FunctionExpand[D[(x^2),{x,n}]]/.n->2

D[(x^2)Exp[x],{x,n}]//Simplify

D[(x^2)Exp[x],{x,n}]/.n->0

D[(x^2)Exp[x],{x,0}]

La forma completa interna delle derivate parziali:

D[f[x,y],{x,2},y]//FullForm

Ripeto: Derivative fa la derivata del simbolo f, non dell’espressione f[x,y]:

Derivative[1,0][f][x,y]

Derivative[1,0][f][x,y]/.f->Function[{x,y},(x^y)]

Derivative[1,0][f][x,y]/.f->Power

Derivative[1,0][f[x,y]]

Derivative[1,0][f[x,y]]/.f->Power

Inghippi

Questa notazione con gli esponenti (f^((1,0)))[x,y] però è traditrice, perché se si cerca di riprodurla in una cella di input non verrà accettata:

(f^((0,1)))[x,y]

La cella seguente è stata ottenuta per copia-e-incolla da un output; sembra identica alla precedente, ma questa è accettata, mentre l'altra no:

Derivative[1,0][f][x,y]

Derivative[1,0][f][x,y]

Cosa succede??? Il fatto è che Mathematica, dietro alle quinte, quando produce la formula (f^((1,0)))[x,y] nell'output, ci nasconde dentro un'informazione extra su come va interpretata. Vediamo l'espressione completa sottostante:

RowBox[{SuperscriptBox["f", 
   TagBox[
    RowBox[{"(", 
     RowBox[{"1", ",", "0"}], ")"}],
    Derivative],
   MultilineFunction->None], "[", 
  RowBox[{"x", ",", "y"}], "]"}]

TagBox

L'inghippo sta in quel TagBox e nel Derivative. Non è questa la sede per approfondire la struttura. Fatto sta che, quando copiamo l'output, queste criptoinformazioni vengono conservate, e incollandolo come input l'oggetto viene interpretato correttamente. Invece, se inseriamo manualmente carattere-per-carattere quello che appare all'occhio, la criptoinformazione non viene inserita, e otteniamo qualcosa che sintatticamente non è accettabile:

Superscript[f,(1,0)][x,y]

Infatti quando si apre una parentesi tonda, questa va richiusa prima di scrivere la virgola per passare all'argomento seguente.

Grafici di funzioni di due variabili

Il grafico di una funzione di due variabili si fa con Plot3D:

f[x_,y_]:=Sin[x y];
Plot3D[f[x,y],{x,-1,1},{y,-1,1}]

Anche qui non era necessario aver definito f[x,y]; si poteva inserire direttamente la formula:

Plot3D[Sin[x y],{x,-π,π},{y,-1,1}]

Funzioni definite con Function

Per definire una funzione esiste anche il costrutto Function:

Function[t,(t^2)]

Si può anche usare la notazione equivalente con la freccia |->, che si introduce con EscfnEsc

t|->(t^2)

Attenzione! Il simbolo "↦" che si trova sulla tavolozza

nonché quello generato col nome TEX Esc\mapstoEsc, assomigliano molto a quello di Function, ma sono "inerti": vanno usati soltanto per il typesetting, non per i calcoli!

Per calcolare una Function in un punto, si aggiunge in coda il valore della variabile fra quadre:

Function[t,(t^2)][3]

(t|->(t^2))[3]

Posso anche assegnare un nome alla funzione:

f=Function[t,(t^2)];
g=x|->(Sqrt[x]);

f[5]

g[4]

f'[x]

Plot[{f[x],g[x]},{x,-2,2}]

Di solito il costrutto Function viene abbreviato in una forma un po' criptica: per prima cosa si indica la variabile indipendente (se è una sola) col cancelletto #, e poi invece di scrivere Function si scrive & alla fine (funzione anonima):

Function[((#)^2)]

((#)^2)&

((#)^2)&//FullForm

#//FullForm

Abbiamo già incontrato espressioni come queste parlando dei numeri algebrici e dei numeri analitici:

Reduce[(x^5)-3(x^3)+1==0&&0<x<1,x]

Reduce[(x^5)-3Cos[x]==0&&-3<x<3,x]

Quando occorre una funzione di più variabili, queste si possono indicare con #1, #2, ecc.:

#1+((#2)^2)&

#1+((#2)^2)&[x,y]

#1+((#2)^2)&[3,1]

#2//FullForm

Volendo usare la notazione con la freccia |->, si mettono le variabili in una lista fra graffe:

f={x,y}|->x+(y^2);
f[a,3]

Ci vuole dell'esercizio per decifrare mentalmente espressioni contenenti cancelletti e &.

Il costrutto Function a volte ci viene imposto dagli output di Mathematica, ma possiamo chiederci se ci sono dei casi in cui ci conviene introdurlo di nostra iniziativa. Un uso è nella programmazione, quando serve una funzione usa-e-getta, per la quale non vale la pena di introdurre un nome simbolico. Un uso di tipo matematico è per esempio quando si vuole fare una sostituzione di una funzione generica con una particolare. Supponiamo di volere sapere quanto vale la seguente espressione

Clear[f];
f''[x]-2f'[x]+f[x]

quando la funzione f è l'esponenziale. Un modo ingenuo è

f''[x]-2f'[x]+f[x]/.f[x]->(E^x)

Purtroppo viene sostituito soltanto f(x) ma non f'(x),f''(x). Si dà il caso che l'esponenziale abbia anche un simbolo suo, Exp. Usiamo quello, senza la variabile x:

f''[x]-2f'[x]+f[x]/.f->Exp

Questo va bene! Mettiamo ora che voglia trovare quanto vale l'espressione quando f(x)=x (e^x), una funzione che non ha un suo simbolo predefinito. Il metodo ingenuo non funziona:

f''[x]-2f'[x]+f[x]/.f[x]->x (E^x)

Un metodo efficace ma lungo è di definire un nuovo simbolo:

g[x_]:=x (E^x);
f''[x]-2f'[x]+f[x]/.f->g

Usando Function posso fare tutto senza nuovi simboli:

f''[x]-2f'[x]+f[x]/.f->Function[t,t (E^t)]

f''[x]-2f'[x]+f[x]/.f->(t|->t (E^t))

f''[x]-2f'[x]+f[x]/.f->(#(E^(#))&)

f'[x]-2f[x]/.f->Function[x,(E^(2x))]

f'[x]-2f[x]/.f->((E^(2#))&)

Se uso la notazione con & devo stare attento alle parentesi, perché & ha una precedenza sintattica molto bassa. Osservate cosa succede:

f'[x]-2f[x]/.f->(E^(2#))&

equivale a

(f'[x]-2f[x]/.f->(E^(2#)))&

che non è quello che mi serve.

Il comando DSolve  restituisce oggetti del tipo Function qualora la funzione incognita sia stata scritta senza la variabile indipendente:

DSolve[x''[t]==-x[t],x[t],t]

DSolve[x''[t]==-x[t],x,t]

La forma con Function è pratica se devo sostituire in formule contenenti derivate. Per esempio, verifichiamo che l'energia dell'oscillatore armonico è costante:

Simplify[((x'[t])^2)+((x[t])^2)/.DSolve[x''[t]==-x[t],x,t]]

Simplify[((x'[t])^2)+(ω^2)((x[t])^2)/.DSolve[x''[t]==-(ω^2)x[t],x,t]]

Funzioni definite a tratti (Piecewise)

Si possono definire delle funzioni a tratti con Piecewise: la sintassi è

Piecewise[{{(Subscript[val, 1]),(Subscript[cond, 1])},{(Subscript[val, 2]),(Subscript[cond, 2])},…}]

Per esempio il valore assoluto per numeri reali

Clear[f,x];
f[x_]=Piecewise[{{x,x≥0},{-x,x<0}}]

f[2]

f[-5]

Plot[f[x],{x,-1,1}]

Esiste anche la notazione bidimensionale, che scatta con EscpwEsc; si aggiunge una colonna con Ctrl[,] e si aggiunge una riga con Ctrl[↵]. È disponibile anche dalle tavolozze Palettes ▶ Basic Math Assistant (Piecewise[{{□, □}, {□, □}}]):

Clear[g];
g[x_]=(Piecewise[{{x, x≥0}, {-x, x<0}}])

Piecewise può funzionare con qualsiasi tipo di condizione, ma è programmata per gestire intelligentemente le condizioni di tipo uguaglianze-disuguaglianze, in particolar modo quando compaiono in derivate o integrali. Per esempio, tornando al valore assoluto,

Clear[f,x];
f[x_]:=(Piecewise[{{x, x≥0}, {-x, x<0}}])

possiamo calcolare la derivata o la primitiva, che risulta essa stessa un oggetto Piecewise:

f'[x]

∫f[x]\[DifferentialD]x

Si possono anche risolvere equazioni differenziali contenenti oggetti Piecewise:

Clear[f,x];
f[x_]:=(Piecewise[{{x, x≥0}, {-x, x<0}}]);
DSolve[y''[x]+2y'[x]==f[x],y[x],x]

Notare che come primitiva dà una funzione continua in 0.
Si possono anche annidare un Piecewise dentro l'altro:

f[f[x]]

Simplify[%]

Si possono sbrogliare matasse anche intricate:

Simplify[f[1+f[x-1]]]

Simplify[f[1+f[(x^2)-1]]]

Non è necessario che le condizioni dentro al Piecewise siano mutuamente esclusive. La convenzione è che le condizioni vadano provate nell'ordine che è stato scritto, ed entra in vigore la prima che risulta vera:

Clear[g];
g[x_]=(Piecewise[{{x, x≥0}, {-x, x<2}}])

g[1]

Simplify[g[x]]

Se una condizione è True, il valore corrispondente entrerà in vigore ogniqualvolta le condizioni precedenti sono false.

Clear[g];
g[x_]=(Piecewise[{{x, (x^2)+3x-7≥0}, {-x, True}}])

Si può anche assegnare il valore di default mettendolo fuori dalle graffe:

Clear[h,x];
h[x_]=Piecewise[{{x,x≥1},{-x,x<-1}},1]

h[0]

Se non si assegna nessun valore di default, Mathematica assume che il default sia 0:

Clear[h,x];
h[x_]=Piecewise[{{x,x≥1},{-x,x<-1}}]

h[0]

La funzione PiecewiseExpand cerca di riscrivere le condizioni nella forma più esplicita:

Clear[g];
g[x_]=(Piecewise[{{x, 2 (x^2)-x-2≥0}, {-x, True}}])
PiecewiseExpand[g[x]]
Plot[{g[x],2 (x^2)-x-2},{x,-2,2}]

PiecewiseExpand può anche prendere espressioni contenenti valori assoluti, Max, Min e trasformarle in oggetti Piecewise:

PiecewiseExpand[Abs[1-Max[x,-x+1]],x∈Reals]

Senza l'indicazione che x è reale, la conversione a Piecewise è solo parziale:

PiecewiseExpand[Abs[1-Abs[x]]]

PiecewiseExpand[Abs[1-Abs[x]],Element[x,Reals]]

Una funzione con iterate caotiche:

f[x_]=Piecewise[{{x+(1/2),x≤(1/2)},{-2 (x-(1/2))+1,(1/2)<x}}];
Simplify[f@f[x]]
Simplify[f@f@f[x]]

Plot[{f[x],f@f[x],f@f@f[x],f@f@f@f[x]},{x,0,1}]

Plot[f@f@f@f@f@f@x,{x,0,1}]

Piecewise funziona anche con più variabili:

Clear[h,x];
h[x_,y_]=Piecewise[{{x-y,x≥y},{y-x,True}}]
Plot3D[h[x,y],{x,-1,1},{y,-1,1}]

Clear[h,x];
h[x_,y_]=Piecewise[{{x-y,x≥y},{y-x,True}}]
Plot3D[h[x,y],{x,-1,1},{y,-1,1},ExclusionsStyle->Red]

Funzioni definite con condizioni /; e test

Condition (/;)

Possiamo dare due formule diverse a una funzione a seconda che l'argomento verifichi o no una Condition: (/; slash-semicolon)

Clear[f];
f[x_/;x≥0]:=x;
f[x_/;x<0]:=-x;

f[1]

f[-1]

Plot[f[x],{x,-1,1}]

Se l'argomento non soddisfa nessuna delle condizioni, Mathematica restituisce l'input senza fare niente:

f[x]

f[I]

Un modo per definire un valore di default è di aggiungere una definizione senza condizioni. Mathematica applica la forma meno "generale" qualora le più particolari non siano soddisfatte:

Clear[f,x];
f[x_/;x≥0]:=x;
f[x_/;-1<x<0]:=-x;
f[x_]=0;

?f

f[-2]

Plot[f[x],{x,-3,3},Frame->True]

Condition è un costrutto programmatico di basso livello. Le funzioni definite usando Condition nel calcolo numerico sono nettamente più veloci di funzioni equivalenti che usano Piecewise:

Clear[f,g,x];
f[x_/;x≥0]=x;
f[x_/;x<0]=-x;
g[x_]=(Piecewise[{{x, x≥0}, {-x, True}}]);

Timing[Do[f[1],{1000000}]]

Timing[Do[g[1],{1000000}]]

Condition accetta condizioni che non sono soltanto uguaglianze o disuguaglianze algebriche, ma qualsiasi costrutto sensato che dia valore vero o falso:

Clear[f,g,z];
f[x_/;x∈Rationals]:=1/Denominator[x];
f[x_/;Not[x∈Rationals]]=0;

f[2]

f[4/5]

f[1.2]

f[(Sqrt[2])]

f[x]

Un grafico di questa strana f sui razionali:

Graphics[Table[Point[{n/Prime[m],f[n/Prime[m]]}],{m,2,10},{n,1,Prime[m]}],Frame->True]

Graphics[]

Una funzione che dà il grado del polinomio minimo di un numero algebrico:

g[x_/;x∈Algebraics]:=Exponent[MinimalPolynomial[x,z],z];
g[x_/;x∉Algebraics]:=0;

g[(Sqrt[1-4(Sqrt[3])])]

Per funzioni come queste, Piecewise non è proibito, ma non è di alcun aiuto, e rallenta l'esecuzione.

Con Condition non c'è aiuto intelligente per dirimere o semplificare le condizioni:

Clear[f];
f[x_/;x≥0]:=x;
f[x_/;x<0]:=-x;

f'[x]

(∫_(-1)^1)f[x]\[DifferentialD]x

f[f[x]]

Simplify[f[f[x]],x>0]

PiecewiseExpand[f[f[x]]]

PatternTest (?)

Esiste anche un altro modo di filtrare l'input, che è PatternTest, che ha questa sintassi:

f[x_?funzione]=…

che è equivalente alla seguente Condition:

f[x_/;funzione[x]]

ma fa risparmiare di riscrivere la variabile x. Le funzioni predefinite Positive, Negative, NonPositive, NonNegative, OddQ, EvenQ, PrimeQ, PrimePowerQ, NumberQ, NumericQ, MachineNumberQ sono molto usate come PatternTest. Per esempio possiamo definire il valore assoluto anche così:

Clear[f,x];
f[x_?NonNegative]=x;
f[x_?Negative]=-x;

f[1]

f[-3]

La funzione di Collaz:

Clear[f,n];
f[n_?OddQ]=3n+1;
f[n_?EvenQ]=n/2;

f[5]

f[6]

Una funzione lineare:

Clear[f,x,y,λ];
f[x_+y_]:=f[x]+f[y];
f[λ_?NumericQ*y_]:=λ*f[y];

f[a+2b-(4/3)c]

PatternTest è un'occasione per usare Function e la notazione con #&, sebbene non si vedono vantaggi di concisione rispetto alla Condition. Per esempio, una funzione che ha valori diversi a seconda della classe di resto modulo 3:

Clear[f,n];
f[n_?(Mod[#,3]==1&)]=n+1;
f[n_?(Mod[#,3]==2&)]=n-1;
f[n_?(Mod[#,3]==0&)]=n;

DiscretePlot[f[n],{n,1,10}]

Filtrare con la “testa” di un'espressione

Un altro modo di filtrare l'input per una funzione è di usare la testa (Head) dell'input. Per esempio, i numeri interi 1, 2, ecc. hanno Integer come testa; le frazioni 1/2, 5/3 ecc. hanno Rational, i numeri in virgola mobile Real, i numeri complessi Complex:

Head[4]

Head[3/4]

Head[3.4]

Head[1+I]

Per specificare che una certa forma di una funzione va usata quando l'input ha una certa testa, si usa la sintassi f[x_testa]: per esempio, una funzione che raddoppia gli interi ma manda a zero i razionali:

Clear[f];
f[x_Integer]:=2x;
f[x_Rational]:=0;

f[1]

f[3/2]

Una funzione che cerca di fattorizzare espressioni che appaiono come somme, ed espande espressioni che trova come prodotti:

Clear[f];
f[x_Plus]:=Factor[x];
f[x_Times]:=Expand[x];

f[(a^2)-(b^2)]

f[a( b+c)]

Esercizi

Disegnare i grafici delle funzioni (Sqrt[x]), ((x-1)/((x^2)+2)), ((Sin[x])/x) per x fra 0 e 2.

Disegnare il grafico di ((x y)/((x^2)+(y^2)+1)) per x e y fra -1e 1.

Disegnare i grafici delle due funzioni (con lo stesso secondo membro ma una ritardata e l'altra immediata): f[x_]:=(x^2)+Random[] e di g[x_]=(x^2)+Random[] per x fra -1 e 1. Come spiegate la differenza?

Definire f[x_,y_]:=Factor[(x^2)-(y^2)] e g[x_,y_]=Factor[(x^2)-(y^2)] (stesso secondo membro ma una ritardata e l'altra immediata), e poi calcolare f[(x^2)-1,(y^2)-1] e g[(x^2)-1,(y^2)-1]. Il risultato è identico? È equivalente? Come lo spiegate?