Calcolo letterale, sostituzioni, assegnazioni

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

Ultima modifica: 19 dicembre 2017

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.
Qualcuno vorrà stampare per leggere su carta: in tal caso apra i gruppi di celle ed esegua tutte le celle prima di stampare. Le celle si possono eseguire tutte in un sol colpo col menu Evaluation > Evaluate Notebook. Oppure selezionare tutto e poi mandare in valutazione.

Inizializzazione

Inizializzazione consigliata:

SetOptions[$FrontEnd,CommonDefaultFormatTypes->{"Output"->StandardForm}];

Espressioni algebriche

Le quattro operazioni e la potenza

Si possono scrivere formule semplici o complicate. La precedenza delle operazioni è quella convenzionale in matematica. Nel dubbio usare le parentesi tonde.

a+b

b+a

a+b+a

a+(b+a)

a+b*c

(a+b)*c

(a+b)c

(a-b)/c

a-b/c

a/b+c

a*-b

(a^2)(b^3)

a/2b

a/b/c

a^b^c

Approfondimento: FullForm

Osservate come il Kernel vede queste espressioni:

FullForm[a+b]

FullForm[a+2b]

FullForm[(a^2)]

FullForm[1/a]

FullForm[a/b]

Expand

Mathematica non applica automaticamente la proprietà distributiva su espressioni letterali: lo fa solo a richiesta tramite Expand:

Expand[(a+b)c]

Expand[(a-b)(a+b)]

Expand[(((a+b))^2)]

Expand[(((a+b))^3)]

Expand[(((a+b))^4)]

Lo trovate nella tavolozza:

Expand non agisce all'interno di funzioni non polinomiali:

Expand[a(b+c)+(Sqrt[e(f+g)])]

Se si vuole spingere la distributività anche dentro ogni sottoespressione, si può usare ExpandAll:

ExpandAll[a(b+c)+(Sqrt[e(f+g)])]

Per applicare una funzione a un'espressione, a volte viene più naturale la sintassi espressione//funzione, invece di funzione[espressione]:

(a+b)c//Expand

Le potenze del binomio, presentate in vari modi

Un piccolo esempio di output interattivo tramite Manipulate:

Manipulate[Expand[(((x+y))^n)],
{n,2,8,1}]

La parte {n,2,8,1} significa che la variabile n va da 2 a 8, a passi di 1.

Una variante in cui invece del cursore c'è una fila di tasti:

Manipulate[Expand[(((x+y))^n)],
{n,2,8,1,Setter}]

La lista delle prime 8 potenze intere del binomio:

Table[Expand@(((x+y))^n),{n,1,8}]

La parte {n,1,8} istruisce Table di dare alla variabile n i valori interi successivi da 1 a 8.

La lista in formato colonnare:

Column@Table[Expand@(((x+y))^n),{n,1,8}]

La lista in formato colonnare con le righe centrate:

Column[Table[Expand@(((x+y))^n),{n,0,8}],Center]

Con FlipView si passa da una potenza alla successiva cliccando sull'output:

FlipView@Table[Expand@(((x+y))^n),{n,1,8}]

La seguente espressione usa un doppio uguale ==

Manipulate[(((x+y))^n)==Expand[(((x+y))^n)],
{n,2,8,1,Setter}]

(Nell'output tradizionale non si nota quasi la differenza fra = semplice e == doppio).
Con un uguale semplice = l'espressione avrebbe dato errore. Provare!
Sull'uguale doppio parleremo ampiamente in séguito.

Factor

Per fattorizzare un'espressione usare Factor:

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

Factor[(x^2)+3x*y-4(y^2)]

Trabocchetto: se scrivete xy senza niente fra i due caratteri, Mathematica prende xy come il nome di una singola variabile, e non come il prodotto delle due variabili x e y:

Factor[(x^2)+3xy-4(y^2)]

Ricordarsi di mettere un asterisco, o uno spazio, fra la x e la y.

Cliccare sull'esponente per avere la fattorizzazione di (a^n)-(b^n):

Manipulate[Factor[(a^n)-(b^n)],{n,2,8,1,Setter}]

Un TabView dell'effetto di Expand e di Factor:

expr=a(a-b)+b(a-b);
TabView[{"originale"->expr,
"Expand"->Expand@expr,
"Factor"->Factor@expr}]

Per default Factor fattorizza sui razionali:

Factor[(x^2)+x-1]

Si può dire a Factor di fattorizzare su estensioni dei razionali ottenute aggiungendo uno o più numeri algebrici:

Factor[(x^2)+x-1,Extension->Sqrt[5]]

Factor[(x^2)+x-1,Extension->Automatic]

Factor[(x^4)-9,Extension->{Sqrt[3],I}]

Simplify

Un'operazione molto potente è Simplify, che cerca una forma più semplice che può dell'espressione data:

(y^3)-(x^3)+a+3(x^2)y-3x*(y^2)//Simplify

Se non si è soddisfatti del risultato di Simplify, provare con l'ancora più potente (ma molto più lento) FullSimplify:

(y^3)-(x^3)+a+3(x^2)y-3x*(y^2)//FullSimplify

Purtroppo anche FullSimplify a volte non si accorge di certe semplificazioni possibili:

1+(((1-y))^3)//Expand

2-3 y+3 (y^2)-(y^3)//FullSimplify

1+(((1-y))^3)//Expand//FullSimplify

Questo output sarebbe quello che ci aspettiamo da Factor, non da Simplify o FullSimplify.

Simplify presuppone che le variabili nell'espressione siano in generale nel campo complesso ℂ. Quindi non fa semplificazioni che nei complessi sono false, come per esempio

Simplify[(Sqrt[(x^2)])]

Si può però dare un'assunzione a Simplify, mettendola come secondo argomento:

Simplify[(Sqrt[(x^2)]),x≥0]

Simplify[(Sqrt[(x^2)]),x<0]

Approfondimento: la struttura ad albero delle espressioni

Tecnicamente parlando, Simplify cerca di  applicare regole di trasformazione in modo da ridurre il numero di "foglie" nell'albero che costituisce l'espressione. Diamo una rapida occhiata all'albero dell'espressione:

(y^3)-(x^3)+a+3(x^2)y-3x*(y^2)

a-(x^3)+3 (x^2) y-3 x (y^2)+(y^3)//TreeForm

TreeForm[a-(x^3)+3 (x^2) y-3 x (y^2)+(y^3),VertexLabeling->Automatic]

Il numero di foglie:

(y^3)-(x^3)+a+3(x^2)y-3x*(y^2)//LeafCount

L'albero semplificato:

(y^3)-(x^3)+a+3(x^2)y-3x*(y^2)//Simplify//TreeForm

In FullForm il numero razionale 1/3 appare come un’espressione composta di tre elementi (foglie) Rational, 1, 3:

FullForm[1/3]

Però in TreeForm c’è una sola foglia:

TreeForm[1/3]

AtomQ[1/3]

Succede una cosa simile per i numeri complessi: notare che la FullForm  non contiene l’unità immaginaria

FullForm[1+I]

TreeForm[1+I]

AtomQ[1+I]

Il numero di foglie nell'albero semplificato:

(y^3)-(x^3)+a+3(x^2)y-3x*(y^2)//Simplify//LeafCount

Le espressioni e gli alberi assieme:

expr=(y^3)-(x^3)+a+3(x^2)y-3x*(y^2);
TabView[{originale->expr,albero->TreeForm@expr,semplificato->Simplify@expr,alberoSemplificato->TreeForm@Simplify@expr},Alignment->Center]

Together, Apart, Cancel

Per raccogliere a denominatore comune un'espressione fratta c'è Together:

Together[(a/(b+c))+(b/(b-c))]

Viceversa, per riscrivere una frazione come somma di fratti semplici c'è Apart:

Apart[(1/(x(x+1)))]

Apart lavora sui razionali:

Apart[(1/((x^2)+x-1))]

Per fare una decomposizione su dei numeri algebrici bisogna invocare Factor:

Apart[Factor[(1/((x^2)+x-1)),Extension->(Sqrt[5])]]

Mathematica cancella automaticamente un fattore comune esplicito a numeratore e denominatore:

(((x+1)(x-1))/(x-1))

Se il fattore è implicito si può usare Cancel (o anche Simplify):

(((x^2)-1)/(x-1))

Cancel[(((x^2)-1)/(x-1))]

Ricordarsi che quando si semplifica in questo modo la nuova espressione può non essere esattamente equivalente all'originale. Negli esempi qui sopra le espressioni originali non hanno senso quando x=1, mentre le semplificate sì.

Attenzione che Mathematica esegue la cancellazione in automatico quando il fattore comune è esplicito:

(x/(x(x+1)))

A volte questo fatto dà risultati inattesi a dei calcoli letterali.

Sostituire una variabile con un valore dentro una data espressione (slash dot /.)

Quanto vale (x^2)-2 quando x=(Sqrt[3])? La sintassi per avere una risposta a questiti simili in Mathematica è

espressione /. variabile -> valore

expr/.rules

/. in inglese si legge "slash dot". L'interfaccia utente sostituisce automaticamente i due caratteri -> con la freccia ->

(x^2)-2/.x->(Sqrt[3])

Internamente /. è ReplaceAlle -> è Rule:

TreeForm[Hold[x/.x->2]]

La lettura “ufficiale” di un’espressione si può ottenere così:

SpokenString[HoldForm[(x^2)-2/.x->(Sqrt[3])]]

In teoria si potrebbe anche sentirla letta a voce alta dal computer così:

Speak[HoldForm[(x^2)-2/.x->(Sqrt[3])]]

Purtroppo la mia macchina legge all’italiana il testo inglese, atroce!

Come già detto, (((x^2)-1)/(x-1)) non ha senso per x=1:

(((x^2)-1)/(x-1))/.x->1

(((x^2)-1)/(x-1))//TreeForm

mentre la semplificazione non ha problemi col valore x=1:

Simplify[(((x^2)-1)/(x-1))]/.x->1

Se vogliamo sostituire dei valori a più di una variabile raccogliamo le assegnazioni fra graffe (una "lista"):

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

Il comando seguente fa prima una sostituzione, e poi nel risultato ne fa una seconda:

(a^2)-(b^2)/.{a->c}/.{c->1}

Notate che le tre espressioni seguenti danno lo stesso risultato:

a/.a->1

a/.(a->1)

a/.{a->1}

mentre invece guardate cosa succede qui:

a/.{{a->1}}

Quando si dà un doppio livello di graffe nelle regole di sostituzione, questo si traduce in un singolo livello di graffe nell'output:

a/.{{a->1},{a->2},{a->3}}

a/.{a->1,a->2,a->3}

Incontreremo questo comportamento parlando delle soluzioni di equazioni, che Mathematica formatta come regole di sostituzione con doppio livello di graffe.

Un pulsante che spara testa o croce a caso:

Button["testa o croce?",Print[RandomInteger[]/.{0->"testa",1->"croce"}]]

Usa RandomInteger[],  che genera a caso 0 o 1, e poi converte lo 0 in testa e l'1 in croce, usando una regola di sostituzione.

Assegnazione di un valore a un simbolo (=, With)

La sostituzione con "slash dot" vale soltanto sull'espressione corrente, e non ha seguito:

1+x/.x->1

Se voglio che d'ora in poi (fino ad esplicito avviso contrario) la x valga 1 posso usare l'assegnazione con =

x=1

D'ora in avanti il nòcciolo sostituisce d'ufficio la x con 1:

1+x

Internamente = è

Internamente = è Set:

TreeForm[Hold[x=1]]

Osservate la colorazione sintattica: prima di dare un valore alla x, il simbolo x era colorato in blu scuro, dopo che si è dato un valore la x diventa nera.

Posso cambiare l'assegnazione:

x=2

1+x

Se voglio ripristinare la x senza valore posso scrivere Clear[x], o anche x=.

x=.

1+x

x=1

Clear[x]

x

Appena si cancella il valore della x, il simbolo torna a essere blu.

Assegnando un nome a un'espressione posso poi manipolarla senza riscriverla ogni volta:

espr=(((x^2)+x-2)/((x^2)-1))-(3/2)(x^2)

Per esempio ora posso confrontare l'effetto di tre trasformazioni diverse:

Together[espr]

Factor[espr]

Simplify[espr]

TabView[{"originale"->espr,Together->Together@espr,Factor->Factor@espr,Simplify->Simplify@espr}]

oppure posso sostituire diversi valori a x:

espr/.x->-(Sqrt[2])/3

espr/.x->1

espr//TreeForm

Simplify[espr]/.x->1

x=3

espr

x=.

espr

?x

x=3

?x

x=.

Per sapere se a un simbolo è assegnato un valore, si può eseguire il simbolo in isolamento:

espr=(((x^2)+x-2)/((x^2)-1))-(3/2)(x^2);

espr

oppure anche scrivere punto interrogativo seguito dal simbolo, e poi eseguire:

?espr

Non conviene abituarsi ad assegnare valori a variabili con una singola lettera: a,b,x,y, ecc. Infatti è facile dimenticarsi di averlo fatto e poi incontrare risultati inaspettati. Qui la colorazione viene in aiuto: i simboli in blu non hanno un valore associato, quelli neri sì. Una strategia migliore può essere quella di raccogliere i valori preferiti in una lista di sostituzioni:

valori={g->9.8,c->300000}

e poi sostituire solo all'occorrenza con /.

(1/2)g (t^2)/.valori

(1/2)g (t^2)

Un costrutto alternativo che si comporta diversamente è con With:

With[{x=(Subscript[x, 0]),y=(Subscript[y, 0]),…},expr]

With[{g=9.8},(1/2)g (t^2)]

g

Il With assegna un valore alle variabili, ma:

soltanto al suo interno

prima che venga fatto alcun altro calcolo

solo nelle variabili che compaiono esplicitamente nell’espressione

With[{g=9.8},(1/2)g (t^2)]

g

Notate la colorazione: la variabile locale g dentro al With è in verde. È una variabile locale

Lo schema sintattico With[{x=(Subscript[x, 0]),y=(Subscript[y, 0]),…},expr] compare scrivendo With e poi scegliendo il menu Edit > Make Template

o con la sua abbreviazione da tastiera. Il trucco Edit > Make Template si può usare con tutte le funzioni predefinite in Mathematica.

Un altro modo ancora di sostituire è di definire una funzione:

f[x_]=(((x^2)+x-2)/((x^2)-1))-(3/2)(x^2);
f[2]

Punto e virgola

Se alla fine di un'espressione di input mettiamo un punto e virgola, il nòcciolo la calcola, ma non mostra il risultato sull'interfaccia utente. Provare a eseguire la cella seguente:

Expand[(x-y)(x+y)];

Un caso in cui questo viene comodo è quando si fa un'assegnazione: di solito non ci interessa rivederne il valore nell'output. Confrontate senza e con punto e virgola:

a=3+(Sqrt[2]+3^(1/3))^3

a=3+(Sqrt[2]+3^(1/3))^3;

Si possono anche dare più istruzioni in una stessa cella, separate da punti e virgola. Verranno calcolati in successione, e viene mostrato il risultato soltanto dell'ultimo (se non finisce anche lui col punto e virgola):

Clear[x,a,b];
a=x-1;
b=x^2-1;
Simplify[a/b-b/a]

Confrontare con la stessa espressione ma senza i punti e virgola:

Clear[x]
a=x-1
b=x^2-1
Simplify[a/b-b/a]

Il punto e virgola ha anche un altro uso: quello di combinare insieme più espressioni in un unico blocco, da inserire come argomento di funzioni di più variabili. Ad esempio, With richiede due argomenti:

With[{x=(Sqrt[2])},
y=x+1/x;
Together[y]]

l'espressione y=x+1/x;Together[y] è considerata da With come un unico blocco, che è il suo secondo argomento. Se scritto con una virgola invece di punto-e-virgola avrebbe dato errore. Provare!

Gli oggetti separati da punto e virgola internamente sono raggruppati in una CompoundExpression:

TreeForm[Hold[a+b;c+d]]

Esempio: generare polinomi scomponibili su ℤ o ℚ

Come si fa per trovare un polinomio di secondo grado che abbia come radici 1 e 5? Beh, basta scrivere

(x-1)(x-5)//Expand

Supponiamo di voler scrivere un eserciziario sulle equazioni di secondo grado. Possiamo generare un polinomio casuale scomponibile sugli interi con

Expand[(x-RandomInteger[{-4,4}]) (x-RandomInteger[{-4,4}])]

La stessa cosa con un pulsante cliccabile:

Button["scrivimi un polinomio",Print@Expand[(x-RandomInteger[{-4,4}]) (x-RandomInteger[{-4,4}])]]

Forse è il caso di scrivere accanto al polinomio anche la sua scomposizione: per questo usiamo la seguente sintassi:

poli=Expand[(x-RandomInteger[{-4,4}]) (x-RandomInteger[{-4,4}])];
Row@{poli,Spacer[30],Factor[poli]}

Lo stesso effetto ottenuto con un pulsante:

Button["scrivimi un polinomio e la sua scomposizione",poli=Expand[(x-RandomInteger[{-4,4}]) (x-RandomInteger[{-4,4}])];
Print@TabView@{Polinomio->poli,scomposizione->Factor[poli]}]

Ogni volta (beh... forse non ogni volta...) che si manda in esecuzione il comando qui sopra viene un polinomio diverso. Possiamo ripetere il comando per esempio 10 volte con Do:

Do[Print@Expand[(x-RandomInteger[{-5,5}]) (x-RandomInteger[{-5,5}])],{10}]

Il seguente comando produce polinomi di secondo grado con una radice intera e una razionale:

Button["scrivimi un polinomio",Print[Expand[(x-RandomInteger[{-5,5}]) (x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}])))]]]

Se vogliamo polinomi a coefficienti interi basta che facciamo denominatore comune con Together e poi prendiamo il numeratore con Numerator:

Button["scrivimi un polinomio e la scomposizione",Print[Row[{poli=Numerator[Together[Expand[(x-RandomInteger[{-5,5}]) (x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}])))]]],Spacer[20],Factor@poli}]]]

Il seguente comando produce dei polinomi di secondo grado con due radici razionali:

Do[Print[Numerator[Together[Expand[(x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}]))) (x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}])))]]]],{10}]

Aggiungiamo anche le scomposizioni:

Do[poli=Numerator[Together[Expand[(x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}]))) (x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}])))]]];Print[FlipView[{poli,Factor[poli]}]],{10}]

Button["dammi un polinomio da scomporre",pol=Numerator[Together[Expand[(x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}]))) (x-((RandomInteger[{-5,5}])/(RandomInteger[{1,5}])))]]];Print@TabView[{"espanso"->pol,"scomposto"->Factor[pol]}]]

Esercizi

Espandere (x-1)(x+1), e poi (x-1)((x^2)+x+1), e poi (x-1)((x^3)+(x^2)+x+1). Indovinare quindi il risultato dell'espansione di (x-1)((x^4)+(x^3)+(x^2)+x+1).

La divisione è associativa? In altre parole, (a/b)/c=a/(b/c)? Rispondere eseguendo i due membri con Mathematica.  La stessa cosa con la potenza al posto della divisione: (a^(((b^c))))=((((a^b)))^c)?

Mathematica come interpreta le espressioni (matematicamente ambigue) a/b/c e a^b^c?

Mandate in esecuzione a/b/.{a->b,b->a}. Una morale: per scambiare fra loro due variabili non c'è bisogno di introdurne una terza ausiliaria.

Eseguire a/b /. a->b /. b->a. Se il risultato vi sorprende pensate che questo equivale per Mathematica a scrivere (a/b /. a->b) /. a->b. Provare poi con a/b/.(a->b/.a->b).

Scrivere un pulsante che spara polinomi a coefficienti interi con radici in ℤ[(Sqrt[2])].

Conoscete la regola ((((a^b)))^c)=(a^(b c))? A prima vista sembrerebbe che Mathematica non ne sappia niente: provate a valutare ((((a^b)))^c) o Simplify[((((a^b)))^c)], o anche FullSimplify[((((a^b)))^c)]. In realtà se Mathematica si rifiuta di applicare la regola c'è un'ottima ragione: la regola diventa falsa quando entrano in gioco i numeri complessi. Provare a valutare ((((a^b)))^c)==(a^(b c))/.{a->-1,b->(3/2),c->(1/3)}. Mathematica comunque sa che la regola è vera sotto una delle due restrizioni seguenti:
1) a>0, b∈ℝ e c qualsiasi: valutare Simplify[(a^b)^c,{a>0,Element[b,Reals]}];
2) a qualsiasi, ma b,c∈ℤ: valutare Simplify[(a^b)^c,Element[{b,c},Integers]].
(A Simplify e FullSimplify si possono aggiungere condizioni. Ne riparleremo).