Seminario Mathematica

Laboratorio per l'Analisi Quantitativa dei Dati
11-25 Marzo 2014

dott. Crescenzio Gallo
crescenzio.gallo@unifg.it

Una breve panoramica

Front-end

Il front end è il modulo che consente di lavorare con Mathematica sfruttandone non solo le potenzialità di calcolo ma anche le molteplici funzionalità di editor di testi scientifici e di visualizzazione dei dati, ad esempio per mezzo di grafici in due e tre dimensioni. Esso dispone di numerose funzioni e comandi, richiamati tramite la barra dei menù. I fogli di lavoro vengono chiamati notebook e possono essere utilizzati sia per scrivere testi scientifici o semplici commenti, sia per eseguire calcoli e visualizzare grafici.
Da un punto di vista generale, si può semplificare il ruolo del front end, affermando che questo modulo si preoccupa di dialogare con l’utente, attraverso l’acquisizione delle sue richieste e la rappresentazione delle informazioni in una maniera ad egli comprensibile.

La palette contiene una serie di bottoni utili per inserire, in un testo o in una linea di input, i simboli speciali più diffusi nella letteratura scientifica. L’utilizzo delle palette risulta molto comodo quando si usa Mathematica per i calcoli immediati, ossia per inserire richieste di computazioni direttamente dal front end. Infatti, è spesso utile scrivere le espressioni utilizzando la sintassi classica, quella che ricorre ai simboli quali ad esempio la radice quadrata (Sqrt[ ]), l’integrale ∫, la sommatoria ∑, la produttoria ∏ o le lettere dell’alfabeto greco.

Ad esempio, per calcolare la radice quadrata di un numero utilizzando la forma più immediata per il sistema (definita anche InputForm) si potrebbe scrivere Sqrt[n], ma il front end permette di utilizzare il simbolo (Sqrt[n]). Allo stesso modo, se si vuole calcolare la somma di n numeri indicizzati del tipo (Subscript[x, 1]), (Subscript[x, 2]), …, (Subscript[x, n]) si può utilizzare il simbolo di sommatoria (∑_(i = 1)^n)(Subscript[x, i]) al posto della funzione Sum[(Subscript[x, i]),{i,1,n}]. 
Le espressioni che contengono simboli, quali ad esempio (Sqrt[ ])e (∑_(i = 1)^n), vengono anche chiamate espressioni bidimensionali.

Il Kernel

Il kernel ha un compito molto più arduo dal punto di vista dell’efficienza e della computazione, dovendo effettuare tutti i calcoli necessari per arrivare alla risposta sulla base delle richieste inserite dall’utente. 

Una volta che il front end ha appurato che la richiesta può essere inoltrata, traduce l’espressione scritta dall’utente in qualcosa di comprensibile al kernel e glielo invia; a questo punto il kernel esegue le operazioni necessarie attivando gli algoritmi corrispondenti e, una volta portati a termine i calcoli, restituisce la risposta al front end, il quale riconverte le informazioni in un formato comprensibile all’utente mostrandole in video. 

Da un punto di vista generale, si può dire che il kernel è un’ampia e ricca libreria di algoritmi per la risoluzione di calcoli numerici, simbolici e grafici, che può essere ulteriormente ampliata dall’utente semplicemente programmando nuove routine con il linguaggio stesso di Mathematica.

La sintassi e le regole di base

Il notebook è un foglio di lavoro interattivo nel quale, se si desidera ricevere una risposta dal sistema, la domanda formulata deve rispettare una serie di regole.
La prima lettera di una parola-chiave del sistema viene sempre scritta in maiuscolo. Nel caso di funzioni con nomi composti, entrambi i nomi devono essere scritti con la prima lettera maiuscola e, per funzioni il cui nome è composto da una sola lettera, questa sarà ovviamente maiuscola.
La regola appena descritta lascia intuire che Mathematica ha un interprete case sensitive, ossia effettua una distinzione tra lettera minuscola e maiuscola.

In virtù del fatto che i notebook possono essere anche utilizzati come documenti di testo, il tasto ⏎ (Invio) viene interpretato dal front end come un carriage return (carattere di a capo); per questo motivo quando, invece, si intende far valutare il contenuto di una cella bisogna premere contemporaneamente i tasti ⇧+⏎ (shift + invio) oppure il tasto Enter (se presente), per indicare al front end che l’intero contenuto della cella deve essere mandato al kernel per l’interpretazione dei comandi in esso inclusi.

Avendo introdotto i concetti e le nozioni fondamentali per cominciare ad utilizzare Mathematica, si prosegue ora con alcuni esempi di calcolo, che serviranno anche ad introdurre ulteriori principi utili ad approfondire la conoscenza del sistema.

Alcuni esempi di calcolo

Si supponga di voler calcolare le radici di una equazione di secondo grado del tipo (x^2)-4==0. La funzione che ci interessa è la Solve. Quando si chiede al sistema una certa computazione, bisogna indicare almeno due cose: l’operazione da effettuare (Solve nell’esempio) e l’argomento su cui effettuarla (l’equazione (x^2)-4==0 nell’esempio). Il passaggio degli argomenti avviene mediante la coppia di parentesi quadre [] che ne delimitano l’inizio e la fine. Una volta completata la scrittura della richiesta, basta premere i tasti ⇧+⏎ contemporaneamente. Si avrà così:

Solve[(x^2)-4 == 0]

La prima cosa che si nota è che la cella di input viene aggiornata con una etichetta (label) del tipo In[1]:=, mentre la cella successiva, contenente la risposta generata dal kernel, presenta una etichetta del tipo Out[1]:=. Questo sta ad indicare che tutte le operazioni che vengono effettuate durante una sessione di lavoro vengono etichettate e memorizzate dal sistema in ordine cronologico. Tale ordine torna utile nel momento in cui si vuole recuperare l’output di una computazione già eseguita in precedenza, senza dover ripetere le operazioni che hanno generato tale output; infatti mediante l’uso della combinazione di simboli %n si può ottenere nuovamente l’output numero n, senza che venga rielaborato il corrispondente input. Se, in un qualunque momento della sessione di lavoro, si vogliono riutilizzare le radici dell’equazione mostrate nella cella etichettata In[1]:=, basta scrivere:

%1

Ritornando alla scrittura dell’equazione (x^2)-4==0, per ottenere l’elevamento a potenza si può utilizzare sia l’espressione x^2 (chiamata InputForm, la forma standard per l’inserimento degli input con solo testo  standard), sia l’espressione bidimensionale (x^2), ottenibile tramite la palette Basic Math Input. Inoltre, per rappresentare un’equazione, bisogna utilizzare la notazione del doppio uguale (==) tra il primo ed il secondo membro. 
Si riportano ora altre informazioni utili per l’inserimento di simboli ed espressioni bidimensionali direttamente dalla tastiera, evitando l’utilizzo delle palette. Ad esempio, l’espressione seguente:

∫((Log[1+ξ])/(Sqrt[ξ]))\[DifferentialD]ξ

si può scrivere utilizzando la sequenza di tasti di seguito riportata, dove sono sottolineate le sequenze di tasti che corrispondono a simboli speciali. Si noti che le sequenze del tipo EscintEsc indicano che i tasti vanno digitati l’uno dopo l’altro, mentre quelle del tipo ⇧Ctrl[2] indicano che i tasti vanno premuti contemporaneamente nella sequenza specificata:

Ancora un’altra espressione bidimensionale:

(∑_(k=1)^∞)(1/(k^2))

ottenibile tramite la sequenza:

Per inserire le espressioni bidimensionali direttamente da tastiera, in genere, è indispensabile ricorrere all’utilizzo dei tasti di controllo quali ⇧ e Ctrl. Per l’inserimento dei simboli, invece, oltre alla possibilità di utilizzare i tasti di controllo, ad esempio Esc, vi è la possibilità di utilizzare la seguente sintassi:

Appena terminata di scrivere la parentesi di chiusura nella forma sopra riportata, il front end provvede a sostituire quanto scritto con il corrispondente simbolo. Si provi a completare con la parentesi quadra chiusa ] le seguenti espressioni:

La tabella che segue riporta la sintassi da utilizzare per l’inserimento da tastiera di alcuni simboli.

({{Sequenza, Simbolo}})

({{\[EscapeKey], Esc}, {\[RightTriangle], ⊳}, {\[Placeholder], □}, {\[SelectionPlaceholder], ■}, {\[Infinity], ∞}, {\[Implies], => }, {\[Epsilon], ϵ}, {\[Element], ∈}, {\[ForAll], ∀}, {\[Exists], ∃}, {\[And], ∧}, {\[Or], ∨}, {\[CirclePlus], ⊕}, {\[CircleTimes], ⊗}})

Infine, si ricorda che le lettere dell’alfabeto greco si ottengono anche tramite la sequenza di Esc lettera Esc

Un’altra regola di sintassi da ricordare è l’utilizzo delle parentesi. Mathematica interpreta le parentesi in maniera differente rispetto alla tradizionale notazione scientifica. Infatti, ad esempio, per indicare una funzione di una variabile bisognerà scrivere f[x] e non f(x). La tabella che segue riepiloga il significato attribuito da Mathematica alle varie forme di parentesi:

Dunque, scrivere f(2) significa moltiplicare la funzione f per 2, mentre f[2] significa valutare la funzione f nel punto 2.

L’Help in linea

Mathematica offre tre differenti livelli di help, differenziati per il tipo di informazioni fornite. Vi sono due diversi help in linea, ottenibili direttamente dal notebook di lavoro. Se si vuole sapere semplicemente il comportamento di una funzione e quali argomenti utilizzare si può chiedere un help di primo livello utilizzando il simbolo ?

?Solve

Se invece si vogliono sapere ulteriori informazioni sulla funzione si può chiedere un help più descrittivo utilizzando il simbolo ??, che fornisce, oltre al messaggio precedente, anche qualche indicazione aggiuntiva sulle caratteristiche della funzione:

??Solve

I comandi ? e ?? permettono anche l'uso del carattere jolly, l'asterisco *. Se non si ricorda il nome completo della funzione di cui si cercano spiegazioni, si può utilizzare anche solo una parte del nome, sostituendo la parte non nota con il carattere jolly *.  Se ad esempio non si ricorda per esteso il nome Solve, basta scrivere:

??Sol*

Un aiuto, o meglio una facilitazione per la scrittura dei nomi delle funzioni, viene fornita dal front end attraverso il meccanismo del completamento automatico delle parole e delle espressioni. Dopo aver iniziato a scrivere parte del nome di una funzione si può digitare la sequenza di caratteri Ctrl [k]. Se esiste una sola parola riconosciuta da Mathematica che inizia con le stesse lettere digitate, allora viene automaticamente completata la parola; se, invece, vi sono più parole che iniziano con tali lettere, viene mostrato un riquadro con l'elenco di tali parole, da cui si può scegliere quella desiderata:

Per completare automaticamente la parola basta indicare con il puntatore del mouse la parola desiderata o selezionarla utilizzando le frecce di scorrimento della tastiera e digitare successivamente ⏎. 
Un'altra funzionalità del front end, di aiuto durante la scrittura dei comandi, è il completamento automatico dell'espressione, analoga alla precedente. Una volta completata la parola chiave, si può chiedere il completamento dell'espressione da utilizzare con la sequenza di tasti ⇧ Ctrl [k]. Questa funzionalità è molto utile, ad esempio, per i comandi di grafica, che solitamente richiedono una struttura dell'input abbastanza articolata. Provare a digitare ⇧ Ctrl [k] nella linea di input seguente:

Si noti che il completamento automatico del nome funziona anche per i simboli speciali, quelli che possono essere introdotti con la forma \[NomeSimbolo]. Si provi ad esempio a digitare Ctrl [k] nella linea seguente

Come ultima considerazione, si sottolinea che quanto fin qui detto a proposito dell'help in linea, ossia per i comandi ?, ??, Ctrl [k] e ⇧ Ctrl [k], vale sia per le funzioni native del sistema, sia per tutto quanto definito dall'utente.

I messaggi di errore

I messaggi di errore che Mathematica adotta sono numerosissimi e vengono raggruppati in diverse categorie, a seconda della tipologia di problema riscontrato (sintassi non corretta, problemi dell’algoritmo interno, mancanza di dati per la computazione, etc).
Si riporta di seguito una sequenza di operazioni esemplificative che mostra il differente comportamento del sistema, a seconda degli errori commessi. Si osservi cosa succede quando viene digitata una sequenza di caratteri totalmente insignificante dal punto di vista dei comandi di Mathematica.

oiehof wefo wijogiesjgo jfvg dskfv skdmòl

Probabilmente ci si aspettava che il sistema restituisse un messaggio di errore e che non effettuasse alcuna operazione: è accaduto esattamente l'opposto!
In effetti, sebbene l'espressione non assuma nessun significato dal punto di vista dell'utente, per il kernel essa corrisponde ad un'operazione ben precisa e scritta correttamente dal punto di vista della sintassi, ossia corrisponde alla moltiplicazione delle variabili oiehof, wefo, wijogiesjgo, jfvg, dskfv e skdmòl per cui l'output riporta il risultato, ovviamente simbolico, della loro moltiplicazione. Si noti che l'ordinamento delle variabili viene eseguito automaticamente dalla funzione Times (moltiplicazione). 
Cosa succede quando per caso si introduce, oltre alle lettere, qualche carattere a cui il sistema attribuisce un particolare significato?

dfasfaskfl:dasda po.*djwoidj up oproqwporiwp

ajsdf /.dfhhd o

Tralasciando per un momento l'interpretazione dei messaggi, si può intuire la differenza del comportamento del kernel rispetto all'esecuzione precedente. In questo ultimo caso tra le varie parole sono stati inseriti alcuni simboli, in particolare il punto . e la sequenza backslash + punto (/.). Il kernel, quindi, ha tentato di eseguire alcune operazioni associate a tali simboli, andando chiaramente in errore perché gli argomenti passati a tali funzioni non sono nella forma corretta. 
Si riporta ora una serie di esempi in cui l'interpretazione dei messaggi guida l'utente ad individuare gli errori occorsi.

Solve[(x^2)+3x-2]

In questo caso il messaggio parla chiaramente di un errore nella scrittura dell'equazione, quindi va interpretato nel modo seguente: la funzione Solve è stata riconosciuta quindi la sua sintassi è corretta; è il suo argomento che non è scritto nel modo giusto perchè l'espressione (x^2)+3x-2 non è affatto un'equazione ma una semplice espressione algebrica, mancando del simbolo che identifica le equazioni (==) ed il secondo membro dell'equazione stessa. In questo caso, il messaggio di errore suggerisce in maniera evidente la correzione da apportare.

Si supponga, invece, di scrivere la seguente espressione:

Solvi[(x^2)+3x-2==0]

Questa volta il sistema non è in grado di stabilire se è stato commesso un errore di scrittura della parola Solve oppure se si voleva effettivamente utilizzare (o creare) una nuova funzione dal nome Solvi. Mathematica è anche un linguaggio di programmazione, quindi permette di definire nuove variabili e/o funzioni; questo spiega perché il sistema non riesce ad identificare con certezza una situazione di errore come nell’esempio.
Se l’intenzione era quella di calcolare le radici dell’equazione, ossia di utilizzare effettivamente la funzione Solve, verificando la sintassi della cella di input ci si rende conto che al posto della e si è scritta la i.

Infine, una volta eliminati tutti gli errori, si ottiene la risposta corretta:

Solve[(x^2)+3x-2==0]

Sintetizzando, gli esempi precedenti lasciano intuire che, nella maggior parte dei casi, il sistema è in grado di interpretare l'input, capire gli errori e quindi aiutare l'utente nella correzione tramite l'uso di messaggi appropriati. Si riportano altri esempi di messaggi.
Nella linea seguente manca una parentesi graffa di chiusura della lista delle equazioni del sistema da risolvere.

Solve[{a x+3y-2==0,-3x+2y -a==2,{x,y}]

Si noti che alcune volte, soprattutto nel caso degli errori di sintassi, il messaggio di errore si conclude con un'espressione che replica l'input errato, sottolineata in rosso nella parte dove si è verificato l'errore. In tali casi non si ottiene alcun risultato come output, perché è il front end che effettua la verifica di correttezza della sintassi, ed in caso di errore non invia alcuna richiesta al kernel. Diversamente, come visto in precedenza, al verificarsi di errori di altra natura, il kernel risponde restituendo in output la stessa riga di comandi data in input. Il fatto che la riga di output sia la stessa di quella in input appare anche in altre circostanze, quando il kernel non è in grado di soddisfare le richieste anche se non vi sono errori. Ciò accade soprattutto con quelle funzioni chiamate macro-solutori, quali ad esempio Integrate, Solve, DSolve, etc. Infatti, in molti casi tali funzioni non sono in grado di computare alcuna risposta, o per via di limitazioni dell'algoritmo corrispondente o perché non esistono soluzioni per il calcolo richiesto. Ad esempio:

Integrate[(1/x),{x,-1,2}]

Il messaggio in questo caso non è tanto di errore quanto di attenzione nell'interpretazione della risposta fornita dal kernel.

DSolve[θ''[t]+Sin[θ[t]]==0,θ,t]

Infine, un'ultima considerazione sul modo in cui Mathematica supporta l'utente nella scrittura delle espressioni input. Quando si scrive un'espressione le parentesi aperte, per le quali non si è digitata ancora la corrispondente parentesi chiusa, vengono mostrate con un differente colore:

Se si dimentica di chiudere una parentesi ad un certo livello, tutte le parentesi ai livelli superiori rimarranno colorate, come nell'esempio che segue:

in cui, avendo dimenticato di inserire la parentesi graffa di chiusura della lista di equazioni, la successiva parentesi di chiusura della Solve rimane colorata in rosso, sebbene sia correttamente abbinata a quella di apertura. Questo suggerisce all'utente di verificare la correttezza dell'espressione prima di mandarla in esecuzione e generare un errore di sintassi.

Solve[{(x^2)-4y==0,(x^2)+2y==-1,{x,y}]

Solve[{(x^2)-4y==0,(x^2)+2y==-1},{x,y}]

I principi fondamentali

Le parti di un’espressione

Come in tutti i linguaggi simbolici, in Mathematica uno degli aspetti fondamentali è quello del poter gestire le intere espressioni, o anche solo parti di esse. Dunque, come ci si aspetta, esistono diversi comandi per prelevare, modificare, sostituire e, in generale, manipolare le espressioni e le loro componenti. In questo paragrafo vedremo la funzione Part che serve a prelevare oggetti interni ad una espressione. Si consideri la seguente:

variabile=Oggetto[argomento1,argomento2,Oggetto2[argomento21,argomento 22],argomento4]

La lunghezza dell'oggetto assegnato alla variabile è pari al numero dei suoi argomenti, cioè 4. Con la funzione Part è possibile prelevare uno o più oggetti interni alla variabile. Le singole parti che compongono un'espressione sono raggiungibili mediante uno schema di indicizzazione molto semplice: ad ogni parte viene associato un numero che corrisponde all'indice del posto che essa occupa nell'espressione.

Part[variabile,1]

La rappresentazione sintetica per la funzione Part è data dalle parentesi quadre doppie [[ e ]], utilizzate, rispettivamente, per l'apertura e la chiusura. Pertanto, la sintassi dell'istruzione precedente è equivalente a:

variabile[[1]]

Con la Part è possibile anche chiedere la parte di posto zero, che corrisponde alla head dell'espressione:

variabile[[0]]

variabile[[0]]==Head[variabile]

Come ci si aspetta, la Part non può essere utilizzata su oggetti atomici:

numero= 10+3I

numero[[1]]

in quanto gli atomi non hanno una rappresentazione alternativa dalla quale sia possibile estrarre elementi interni, dunque hanno lunghezza zero. 
Con la Part è possibile anche utilizzare la numerazione negativa, che considera l'espressione a partire dalla fine:

variabile[[-1]]

variabile[[-3]]

Opzioni

Le opzioni forniscono ulteriori informazioni ed argomenti alle funzioni per adattare i risultati alle specifiche esigenze dell'utente.

In senso generale, si può affermare che il meccanismo delle opzioni interviene in due momenti diversi: quando si utilizzano le funzioni di carattere prettamente computazionale, per indicare opportune informazioni aggiuntive all’algoritmo che deve risolvere un problema di calcolo; e, come nel caso della grafica e della programmazione del front end, quando si desidera personalizzare il modo in cui appare (layout) un oggetto. Si riportano di seguito alcuni esempi.

Quando si chiede un integrale definito mediante la funzione Integrate, Mathematica cerca di restituire un valore sempre valido indicando,  se è necessario, le condizioni di validità.

(∫_0^∞)(E^(a x))\[DifferentialD]x

Se il risultato ottenuto non è proprio come ci si aspettava, si può provare a visualizzare le opzioni della funzione Integrate, per tentare di modificarne il comportamento. Per sapere le opzioni di un oggetto si può procedere in due modi: utilizzando il ?? o il comando Options

??Integrate

Options[Integrate]

Se si vuole conoscere l'uso ed il significato di una particolare opzione, basta utilizzare uno degli help disponibili.

??Assumptions

Dunque, l'opzione Assumptions permette di indicare a priori le condizioni, se note, sui parametri. Se si è interessati ai soli valori reali minori di zero del parametro a basta indicare tale condizione mediante l'opzione Assumptions.

Integrate[(E^(a x)),{x,0,∞},Assumptions->{a<0}]

In questo caso, lo stesso risultato si ottiene impostando il valore False per l'opzione GenerateConditions.

Integrate[(E^(a x)),{x,0,∞},GenerateConditions-> False]

In pratica le Option sono delle condizioni interne agli algoritmi associati alle varie funzioni, e pertanto devono sempre avere un valore definito. Questo conduce facilmente al concetto del valore di default per ciascuna Option, che viene impostato dal sistema ed utilizzato dal kernel nel caso in cui l'utente ometta le indicazioni in merito. Segue ancora un esempio di opzioni nel caso della funzione Limit.

Limit[(1/((x-1))),x->1]

Options[Limit]

??Direction

Come si legge dal messaggio di help precedente, se si desidera specificare la direzione con cui la variabile indipendente tende al valore limite, basta utilizzare l'opzione Direction:

Limit[(1/((x-1))),x->1,Direction->1]

Limit[(1/((x-1))),x->1,Direction->-1]

Anche per la Limit, in alcuni casi si rende indispensabile l'utilizzo dell'opzione Assumptions per ottenere un risultato corretto:

Limit[(x^α),x->∞]

Limit[(x^α),x->∞,Assumptions->α>0]

Limit[(x^α),x->∞,Assumptions->α<0]

L'altro caso in cui si può influenzare il comportamento del sistema tramite il meccanismo delle opzioni è quello della grafica o del front end. La tabella che segue mostra alcune opzioni inerenti le principali funzioni di grafica in due e tre dimensioni (d'ora in poi indicata come 2D e 3D).

Si mostrano ora alcuni esempi di modifica del risultato grafico ottenuto con Plot.

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

Plot[(x^2),{x,-1,1},Background->GrayLevel[0.8]]

Plot[(x^2),{x,-1,1},Background->GrayLevel[0.8],Axes->False]

Attributi

Mentre le opzioni di una funzione offrono la possibilità di variare il suo comportamento in funzione del valore ad esse assegnato, gli attributi di una funzione indicano alcune sue caratteristiche preimpostate, che possono essere inibite, ma non modificate nella loro influenza sul comportamento della funzione stessa.

Constant

Se si vuole calcolare la derivata di un'espressione con variabili e parametri, potrebbe essere utile indicare i parametri come costanti rispetto alla variabile indipendente, ossia a derivata nulla.

Dt[3(x^3)+ c (x^2)-8c x,x]

A meno di opportune specifiche sul parametro c, Mathematica indica il risultato della precedente derivata in funzione della derivata di c rispetto ad x, essendo c non definito. Se c deve essere inteso come costante, basta impostare per essa l'attributo Constant.

SetAttributes[c,Constant]

Dt[3(x^3)+ c (x^2)-8c x,x]

Protected

L'attributo Protected, assegnato di default a tutti gli oggetti nativi di Mathematica, serve per prevenire eventuali ri-assegnazioni di simboli o funzioni protette. Ad esempio potrebbe capitare di voler utilizzare le variabili D o I, che sono, rispettivamente, il simbolo di derivata parziale  e di unità immaginaria.

D=0

I=0

Come richiamato dai messaggi di errore, tali simboli hanno l'attributo Protected, che evita appunto eventuali assegnazioni dell'utente che potrebbero causare malfunzionamenti al sistema. Ovviamente anche per le proprie routine o variabili si può impostare l'attributo Protected, così da evitare errori involontari da parte di chi poi le utilizza.

Un simbolo protetto non può essere né modificato (assegnazione di un altro valore), né cancellato (rimozione del valore assegnato), né rimosso (eliminazione definitiva dalla sessione corrente). 
L'attributo Protected offre la possibilità di rimuovere la protezione di un oggetto tramite la funzione Unprotect.

Unprotect[a]

a=0

?a

Remove[a]

?a

Rendere permanente il valore di un’opzione

Si individuano tre livelli distinti di validità delle modifiche apportate alle opzioni:
a)   livello locale, validità per il solo oggetto cui si associa l’opzione;
b)   livello di sessione corrente, validità per la sessione di kernel attuale, scompare alla chiusura del kernel;
c)   livello globale, ossia valido per tutte le sessioni di Mathematica.

Se si vuole un particolare colore di sfondo per un grafico basta scrivere:

Plot[Sin[x],{x,-π,π},Background->GrayLevel[0.9]]

In questo caso la modifica avrà validità solo per l’oggetto appena creato, quindi è locale all’oggetto stesso (caso a). Invece, se si desidera che per la sessione corrente di Mathematica (caso b) i grafici abbiano tutti lo stesso colore di sfondo, si può impostare GrayLevel[0.9] come valore di default per l’opzione Background con il comando SetOptions:

SetOptions[Plot,Background->GrayLevel[0.9]]

D'ora in poi tutti i grafici avranno lo sfondo grigio senza che ciò debba essere specificato esplicitamente come opzione nella chiamata della Plot.

Plot[Sin[5 x]+Cos[4 x],{x,-π,π}]

Plot[((x^4)-2 (x^2)+1) (Sqrt[1+(x^2)]),{x,-2,2}]

Questa impostazione per il Background della Plot rimarrà valida fino a quando non si chiude la sessione corrente (chiusura del solo kernel o del sistema Mathematica) o fino a quando non si applica una nuova modifica tramite la SetOptions. Ovviamente anche dopo l'utilizzo della SetOptions è possibile modificare localmente un grafico:

Plot[(Sqrt[1+(x^2)]),{x,-5,5},Background->RGBColor[0.7,0.9,0.9]]

Plot[(Sqrt[1+(x^2)]),{x,-5,5}]

Per ripristinare il valore di default per l'opzione Background della Plot digitare:

Plot[(Sqrt[1+(x^2)]),{x,-5,5}]

Infine, per rendere una modifica valida per tutte le sessioni di Mathematica, si deve  modificare il file di inizializzazione del sistema denominato init.m (su Mac il file si trova nel percorso /Library/Mathematica/Kernel).

Elementi di base

Le liste

Alcuni linguaggi di programmazione adottano un tipo di dati differenziato per vettori, matrici e liste. In Mathematica non si ha una vera e propria distinzione tra le varie strutture dati, essendo tutte viste generalmente come liste. Una lista può contenere qualsiasi cosa, ed in qualsiasi ordine e livello di nidificazione. Il criterio generale è che Mathematica vede una qualsiasi lista come una struttura dati anche quando la sua forma è compatibile con il significato di un vettore, di una matrice o un tensore. Pertanto, soprattutto nell’applicazione delle funzioni del calcolo matriciale è opportuno prestare attenzione ai risultati. Più avanti, in questo paragrafo, si riprenderà tale concetto riportando alcuni esempi.

Costruzione di liste

In Mathematica la struttura dati più comune e più utile è proprio la List, un elenco di elementi (espressioni composte o atomi) delimitato dalle parentesi {}. Per costruire una lista si possono utilizzare diversi metodi. Ad es. scrivendo direttamente gli elementi racchiusi tra le parentesi:

{0,1,2,3,4,5,6,7,8,9}

Oppure si può scrivere:

List[0,1,2,3,4,5,6,7,8,9]

?List

Le espressioni precedenti indicano al sistema di creare un oggetto di tipo lista con dentro le cifre da 0 a 9; per separare gli oggetti all'interno di una lista bisogna utilizzare la virgola. La lista può essere vista come un contenitore di oggetti: all'interno di una lista si possono inserire numeri, variabili, simboli, funzioni, operatori, o anche altre liste ancora.

{simbolo, 2,3,"stringa",(x^2)-4==0,a,w,π,{0,-4}}

Nella lista precedente sono stati inseriti dei numeri, delle costanti, delle stringhe, un'equazione ed una lista. Per creare una lista si possono adoperare diverse funzioni. Il modo più immediato e semplice per costruire le liste è mediante l’utilizzo della funzione Table.

?Table

Table[(x^i), {i,1,5}](* per i da 1 a 5 passo 1 *)

Table[i, {i,3,6,(1/2)}](* per i da 3 a 6 passo 1/2 *)

Table[i, {i,3,6}](* per i da 3 a 6 passo 1 *)

Table[i, {i,6}](* per i da 1 a 6 passo 1 *)

?RandomReal

RandomReal[{0,1},10](* 10 n.ri reali tra 0 e 1 *)

?MatrixForm

?RandomInteger

MatrixForm[RandomInteger[{-10,10},{6,4}]]

?Binomial

Table[Binomial[i,j],{i,5},{j,5}]//MatrixForm

Table[Binomial[i,j],{i,5},{j,i}]//MatrixForm

Il comando Range genera una lista di numeri compresi in un intervallo specificato. Per default il minimo è considerato 1.

?Range

Range[10](* gli interi tra 1 e 10, passo 1 *)

Range[-3,8](* gli interi tra -3 e 8, passo 1 *)

Range[-3,2,(1/2)](* i n.ri razionali tra -3 e 2, passo 1/2 *)

Range[0,10,.25](* i n.ri razionali tra 0 e 10, passo 0.25 *)

?Array

Anche la funzione Array serve per creare liste o liste di liste:

Array[f,5]

Allo stesso modo, se si indica una coppia di valori, si può creare una matrice anziché un vettore:

Array[f,{3,4}]//MatrixForm

La funzione Array offre anche l’opportunità di modificare il risultato ottenuto, sostituendo la head List con un’altra head, quindi utile per generare anche espressioni diverse dalle liste.

Array[f,{3,5},{2,3},Plus](* 3 righe, 5 colonne; indici riga da 2, colonna da 3; head addizione invece che lista *)

Array[f,{5},1,Plus](* 5 elementi; indici da 1; head somma invece che lista *)

La funzione SparseArray risulta molto utile nella creazione di liste laddove esse rappresentano matrici con un elevato numero di elementi nulli. Infatti, SparseArray permette di creare matrici sparse specificando solo gli elementi non nulli. Per default, gli elementi non specificati vengono impostati a zero, ma volendo si può modificare tale impostazione.

?SparseArray

lista=SparseArray[{{1,1}->1,{2,2}->2,{3,3}->3}]

MatrixForm[lista]

listanormale=Normal[lista]

La differenza enorme tra le due forme è che nel primo caso si ha una forte riduzione di spazio di memoria necessario per conservare la matrice, pur mantenendo tutte le informazioni necessarie per operare su di essa. Ovviamente, per piccole matrici tale vantaggio può sembrare banale, ma basta considerare una matrice 100×100 per comprenderne la reale potenzialità.

A=SparseArray[Table[{i,i}->RandomInteger[{1,5}],{i,100}]]

Dimensions[A]

B=Normal[A]; 
Print["B è di tipo ",Head[B]," di dimensioni ",Dimensions[B]]

memA=ByteCount[A](* byte di memoria occupati da A: ∼2KB *)

memB=ByteCount[B](* byte di memoria occupati da B: ∼40KB *)

N[(memB/memA)] "volte più grande!"

?Flatten

Infine, un'altra funzione molto utile relativamente alla manipolazione delle liste è la Flatten, che elimina i livelli di parentesi nelle liste di liste. Ad esempio:

Solve[(x^2)-2x+1==0](* una singola equazione di 2° grado *)

Flatten[%]

Solve[{x-2y+2==0,y+x-4==0}](* sistema lineare *)

ReplaceAll[{x,y},%]

Flatten[%]

Oppure, mettendo tutto insieme nel calssico stile della programmazione funzionale:

Flatten[ReplaceAll[{x,y},Solve[{x-2y+2==0,y+x-4==0}]]]

Un altro caso frequente ricorre quando si uniscono diverse liste tramite Join:

Join[{{a,b,c},{1,2,3}},{d,{e},f}]

Flatten[%]

Data la seguente lista:

lista = {simbolo, 2,3,"stringa",(x^2)-4==0,a,w,π,{0,-4}}

per ottenere l'elemento "stringa", il quarto elemento della lista, bisognerà scrivere:

Part[lista,4]

oppure

lista[[4]]

Data la seguente lista:

lista={{1,2,3},{4,5,6}, {7,8,9}}

si supponga di voler prendere gli elementi 4 e 6 che si trovano nella seconda sottolista alle posizioni 1 e 3, rispettivamente. Un modo di procedere potrebbe essere:

{lista[[2,1]],lista[[2,3]]}

In maniera più sintetica, si può scrivere :

lista[[2,{1,3}]]

Altro esempio: si estraggano dalla lista lista le righe 1 e 3

lista[[{1,3}]]

Infine, se si desidera prendere una sola colonna dalla matrice memorizzata in lista, basta scrivere:

lista[[All,2]]

lista//MatrixForm

L’attributo Listable

E’ possibile eseguire operazioni aritmetiche direttamente sulle liste, come negli esempi che seguono:

basi = {a,b,c}

esponenti = {d,e,f}

(basi^esponenti)

basi * esponenti

basi + esponenti

espressioni = (((basi-x))^(esponenti ))+1

basi = {1,2,3}

esponenti = {4,5,6}

espressioni = (((basi-x))^(esponenti ))+1

Expand[espressioni]//TableForm

D[espressioni,x]

D[Expand[espressioni],x]

Expand[D[espressioni,x]]

Se si desidera conoscere il valore nel punto x=0 delle derivate delle espressioni sopra calcolate si può scrivere:

ReplaceAll[D[(((basi-x))^(esponenti ))+1,x], x->0]

Esportazione

Quando bisogna esportare dati in un particolare formato, ad esempio per problemi di compatibilità con altre applicazioni, bisogna utilizzare la Export. I formati riconosciuti sono indicati dalla variabile $ExportFormats.

$ExportFormats

Ad esempio, vediamo come esportare una lista di valori in formato CSV, compatibile con Excel.

valori=RandomInteger[{1,5},{5,5}]

Directory[]

SetDirectory["/Users/cgallo/Desktop/Seminario\ Mathematica"];

Directory[]

Export["tabella.csv",valori,"CSV"]

FilePrint["tabella.csv"]

Export["tabella",valori,"List"]

FilePrint["tabella"]

Export["espressione.tex",(Sqrt[((x^2)/(((x-1))^2))]),"TeX"]

FilePrint["espressione.tex"]

Tramite la Export si possono convertire anche interi documenti, oltre che singole espressioni.

I macro-solutori

Una delle caratteristiche di Mathematica che lo hanno reso utile ed indispensabile in molti contesti scientifici è la disponibilità di un ampio insieme di macro solutori, ossia funzioni in grado di risolvere, con una sola istruzione, problemi complessi come, ad esempio, il calcolo analitico di un integrale, un limite o un sistema di equazioni differenziali. La tabella che segue riporta un elenco dei macro solutori più frequentemente utilizzati.

Derivata (parziale D, totale Dt)

Oltre alla potenzialità di calcolo, tali macro solutori offono, alle volte, un ulteriore aiuto. Operando nel caso simbolico, essi permettono di esplorare le regole generali che si applicano ai vari calcoli. Ad esempio, nel caso dell’operatore di derivata D, la sua caratteristica di operare anche in maniera simbolica, permette di rivedere le regole generali per il calcolo delle derivate. Chiamando la funzione D con una qualsiasi espressione e/o composizione di funzioni, senza peraltro specificare l’espressione analitica delle funzioni stesse, si ottiene la forma generale della derivata.

D[f[x] + g[x],x] (* Derivata della somma di due funzioni *)

D[f[x] g[x],x] (* Derivata del prodotto di due funzioni *)

D[((f[x])/(g[x])),x] (* Derivata del rapporto di due funzioni *)

In questo ultimo caso la forma data in output potrebbe essere diversa da quella usualmente nota ma, volendo, si può ricondurla a quella tipica tramite l'uso del comando Together che riscrive un'espressione algebrica di più razionali in un'unica espressione con denominatore comune.

Together[%]

D[f[g[x]],x] (* Derivata di funzioni composte *)

D[((f[x])^(g[x])),x]

Per calcolare le derivate successive alla prima, basta indicare la variabile indipendente in una lista, il cui secondo argomento è l'ordine di derivazione:

D[((f[x])^(g[x])),{x,2}]

Anche la funzione Integrate è in grado di operare su espressioni del tutto simboliche. Si provi a calcolare due volte l'integrale del risultato precedente.

Integrate[%,x]

Integrate[%,x]

Seguono altri esempi di derivate.

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

Dt[x y + Log[x] Log[y],x]

Dt[x y + Log[x] Log[y],{x,2}]

Alle volte può essere utile rivedere il risultato con la notazione scientifica tradizionale. Per fare ciò basta aggiungere, dopo il comando, una specifica di visualizzazione di tipo TraditionalForm:

Dt[x y + Log[x] Log[y],{x,2}]//TraditionalForm

Solve e Reduce

Solve[a (x^2)+b x+c==0,x]

Solve[(x^2)+(y^2)-3<0&&x<2y-1,{x,y}]

?Reduce

Reduce[(x^2)+(y^2)-3<0&&x<2y-1,{x,y}]

Reduce[(x^2)+(y^2)-3<0&&x<2y-1,{x,y}]//TraditionalForm

Reduce[(x^2)-5 x+4>=0,x]

??DSolve

Clear[y,t];
DSolve[{y'[t]==-(1/5)y[t]+100},y[t],t]

sols=Table[y[t]/.%[[1]]/.C[1]->n,{n,-500,500,50}]

Plot[sols,{t,0,15}]

??NDSolve

sol=NDSolve[{y'[t]==0.05y[t]-0.0001((y[t])^2),y[0]==10},y[t],{t,0,200}]

Plot[y[t]/.sol[[1]],{t,0,200}]

Uso evoluto di macro-solutori

Ora seguono altri esempi che illustrano situazioni abbastanza frequenti in Mathematica, ossia casi in cui i macro solutori non sono in grado, apparentemente, di fornire una risposta a determinati input. Il concetto sottostante è che molte volte prima di usare una funzione c'è bisogno di manipolare i dati di partenza in maniera tale da riscriverli in una forma non necessariamente più semplice, ma sicuramente più adatta e riconoscibile dagli algoritmi interni ai macro solutori.
Il primo esempio mostra il caso di un limite di una funzione che comprende un coefficiente binomiale.
Sia data la funzione f(n)=(((-1))^n)(({{-(1/2)}, {n}})) (Sqrt[n π]). Andando a calcolare una sequenza di punti della f(n) e visualizzandoli con la funzione ListPlot, si osserva graficamente che essa tende ad 1 per n che tende ad ∞.

ListPlot[punti]

Se si tenta di calcolare tale limite direttamente con la funzione Limit, si osserva che essa non è in grado di fornire una risposta.

Limit[(((-1))^n)(({{-(1/2)}, {n}}))(Sqrt[n π]), n->∞]

Occorre, dunque, tentare di riscrivere l'espressione della f(n) in maniera differente, applicando qualche regola teorica che permetta la semplificazione della sua espressione. Un primo passo è quello di utilizzare la funzione FunctionExpand che modifica l'espressione fornita, mediante l'applicazione di regole e funzioni speciali. In questo caso, FunctionExpand tenta di sostituire il coefficiente binomiale con una combinazione di funzioni Gamma di Eulero Γ(n).

f1=FunctionExpand[(((-1))^n)(({{-(1/2)}, {n}}))(Sqrt[n π])]

Il passo successivo sarà quello di semplificare ulteriormente tale risultato applicando la formula: Γ(n) Γ(1-n) = π csc(π n)

Per applicare tale formula, si utilizza la funzione ReplaceAll. In particolare, per sostituire il termine Γ((1/2)-n) si moltiplica e divide per Γ(1-((1/2)-n)). In tal modo comparirà il termine Γ(n)Γ(1-n) che potrà essere semplificato mediante l'uso della funzione FullSimplify.

f2=ReplaceAll[f1,Gamma[(1/2)-n]->((FullSimplify[Gamma[(1/2)-n]Gamma[(1/2)+n]])/(Gamma[(1/2)+n]))]

Ora si semplifica ulteriormente tale risultato, mediante la funzione Simplify con la specifica che n ∈ ℤ.

f3=Simplify[f2, n∈ℤ]

A questo punto si prova a calcolare il limite per n che tende ad infinito:

Limit[f3, n-> ∞]

Come si vede dal risultato ottenuto, se ci si ferma ad un uso immediato dei macro solutori con i dati disponibili in partenza, si potrebbe erroneamente pensare che Mathematica non sia in grado di manipolare espressioni complesse. Viceversa, andando ad indagare sulle possibili rappresentazioni alternative dei dati disponibili, spesso mediante l’applicazione di regole teoriche o di funzioni di semplificazione disponibili quali, ad esempio, Simplify, FullSimplify, FunctionExpand, PowerExpand, ComplexExpand, etc. si ottiene il risultato desiderato.

Seguono altri esempi, sull’utilizzo dell’opzione Assumptions e sulle notevoli capacità, per i macro solutori, in merito alla specifica del dominio di appartenenza delle variabili o di particolare condizioni su di esse.

Timing[Integrate[(((E^(-a x))-(E^(-b x)))/x),{x,0,Infinity},Assumptions->Re[a]>0&&Re[b]>0]]

Timing[Integrate[(((E^(-a (x^2)))-(E^(-b (x^2))))/x),{x,0,Infinity},Assumptions->Re[a]>0&&Re[b]>0]]

Un metodo alternativo per impostare una condizione su un argomento viene fornito dalla funzione Assuming. La comodità di tale alternativa è che si può specificare una sola volta la condizione su una variabile e poi far seguire una serie di calcoli, con chiamate a diversi macro solutori, che considereranno tutti la stessa condizione impostata. 
L’esempio che segue imposta la condizione σ > 0, poi calcola l’integrale della funzione (e^(-((((x-μ))^2)/(2 (σ^2)))))(1/((Sqrt[2 π]) σ)) ed infine il limite del risultato ottenuto dall’integrale, per y che tende ad infinito. Infine, si aggiunge una lista con la funzione ed il limite come valori da restituire alla fine del calcolo.

Assuming[ 
σ>0,
funzione = (∫_(-∞)^y)(1/((Sqrt[2 π]) σ))(E^(-((((x-μ))^2)/(2(σ^2)))))\[DifferentialD]x;
limite=Limit[ funzione,y->∞];
{funzione,limite}]

N.B. Erf[z] è l’integrale della distribuzione Gaussiana, dato da erf(z)=(2/(Sqrt[π]))(∫_0^z)(e^(-(t^2)))dt.

Un'altra funzione analoga alla Assuming è Refine[espressione, ipotesi], che restituisce la forma che assume l'espressione se i simboli in essa compresi vengono sostituiti da valori che soddisfano le ipotesi. Ad esempio, risulta spesso utile impostare alcune condizioni per le variabili che compaiono in espressioni irrazionali.

var=(Sqrt[x (y^2) (z^4)])

Se è noto che la variabile x è sempre maggiore di zero, si può trasformare l'espressione di var.

Refine[var, x>0]

Se anche y è maggiore di zero:

Refine[var, x>0 && y > 0]

Refine[var, y<0]

Anche la funzione Reduce rientra nella categoria dei macro solutori. Tale funzione, spesso utilizzata in alternativa alla Solve, riscrive l'espressione data in input cercando di risolvere le equazioni e disequazioni in essa presenti ed eliminando i quantificatori eventualmente presenti. In pratica, la Reduce può tornare molto utile per risolvere equazioni e sistemi di equazioni con l'aggiunta di specifiche condizioni sui parametri e sulle variabili.

Solve[ (x^2)-2 (y^2)==1,{x,y}]

Il risultato fornito da Solve non tiene in considerazione alcuna ipotesi sul dominio cui appartengono le variabili, né permette di specificarlo in alcun modo. Con la Reduce, invece, è possibile indicare, ad esempio, il dominio di una o di tutte le variabili. Il comando che segue imposta nella Reduce tutte le variabili come reali.

Reduce[ (x^2)-2 (y^2)==1,{x,y},Reals]

soluzione=Reduce[ (x^2)-2 (y^2)==1 && x∈Reals &&y∈Complexes,{x,y}]

L'output generato da Reduce può essere riscritto come un'unica sequenza di soluzioni alternative (legate dall'operatore logico OR espresso con il simbolo ||), applicandovi la funzione LogicalExpand.

LogicalExpand[soluzione]

Nel caso di equazioni con parametri la Reduce è in grado di fornire anche indicazioni in merito ai parametri, laddove la Solve non esplicita alcuna considerazione e restituisce un risultato che, sotto certe condizioni, potrebbe anche perdere di significato.

Solve[a (x^2)+b x +c==0,x]

Nulla viene detto dalla Solve in merito a cosa accade alla soluzione se il parametro a è uguale a zero. La Reduce risponde allo stesso input evidenziando anche tale situazione.

Reduce[a (x^2)+b x +c==0,x]

L'esempio che segue mostra ancora una volta l'importanza del calcolo simbolico, in particolare nello studio di equazioni letterali. L'introduzione della LogicalExpand, della Apply e della TableForm servono unicamente per visualizzare il risultato in una forma più leggibile.

Apply[List,LogicalExpand[Reduce[a (x^2)+b x +c==0 && (b^2)-4a c >0,x]]]//TableForm

Ora si risolve nuovamente l'equazione (x^2)- 2 (y^2)==1 nel caso di x, y ∈ ℤ. In questo caso si tratta di equazioni diofantinee. L'esempio seguente mostra una forma speciale chiamata equazione di Pell.

Reduce[ (x^2)- 2 (y^2)==1, {x,y},Integers ]//TraditionalForm

Un altro caso di equazione di Pell.

LogicalExpand[Reduce[{(x^2)==5 (y^2)+1,x>0,y>0},{x,y},Integers]]//TraditionalForm

L'equazione di Thue.

Reduce[(x^3)-4 x (y^2)+(y^3)==1,{x,y},Integers]//TraditionalForm

Un altro caso importante in cui la Reduce si distingue dalla Solve è quello in cui si presentano soluzioni multiple, tipicamente con le equazioni goniometriche.

Solve[Sin[x]==0,x]

Come si legge dal messaggio generato da Solve, in questi casi viene espressamente suggerito di utilizzare la Reduce.

Reduce[Sin[x]==0,x]//TraditionalForm

Solve[Sin[x]==Cos[x],x]

Reduce[Sin[x]==Cos[x],x]//TraditionalForm

Reduce[Sin[x]==Cos[x],x,GeneratedParameters->k]//TraditionalForm

Reduce[Tan[x]==0,x,GeneratedParameters->k]//TraditionalForm

Un altro tipo di equazione che la Reduce è in grado di trattare è quello che comprende i quantificatori come ad esempio quello esistenziale (∃) e quello universale (∀), definiti da Mathematica rispettivamente con i termini Exists (∃) and ForAll (∀).
L'esempio mostra la determinazione dei valori di a e b che verificano la disuguaglianza per tutti i valori reali della x.

Reduce[ForAll[ x, x∈Reals,(x^2)+ a x + b≥-1], {a,b},Reals ]

ForAll[ x, x∈Reals,(x^2)+ a x + b≥-1]//TraditionalForm

Reduce[ (Subscript[∀, x,x∈ℝ])(x^2)+a x+b≥-1, {a,b}, Reals ]

Reduce[ForAll[ x, x∈Reals,a (x^2)+ b x + c≥-1 ], {a,b},Reals ]//TraditionalForm

Reduce[ForAll[ x, x∈Reals&&a≥b,a (x^2)+ b x + c≥-1 ], {a,b},Reals]//TraditionalForm

Come visto negli esempi precedenti, la Reduce restituisce la soluzione generica con tutte le condizioni sulle variabili e sui parametri. Spesso risulta utile conoscere una o più soluzioni numeriche per una determinata equazione o sistema. A tale scopo, è stata introdotta la funzione FindInstance che accetta gli stessi input della Reduce ma restituisce un numero, indicato dall'utente, di possibili soluzioni. FindInstance lavora su tutti i problemi per i quali la Reduce è in grado di restituire soluzioni complete. Il vantaggio di FindInstance è duplice: in alcuni casi è in grado di individuare soluzioni per problemi che Reduce non riesce a risolvere; l'individuazione di soluzioni numeriche riduce notevolmente i tempi di esecuzione. 
L'esempio seguente mostra il calcolo delle radici dell'equazione (x^2)- (x^3)-2(y^2)==1.

Reduce[(x^2)- (x^3)-2(y^2)==1, {x,y}, Reals ]

Con FindInstance si richiede di individuare tre coppie di soluzioni numeriche dell'equazione sopra riportata.

?FindInstance

FindInstance[(x^2)- (x^3)-2(y^2)==1, {x,y}, Reals,3]

La funzione FindRoot è in grado di risolvere anche equazioni di variabili vettoriali e matriciali. Si risolve, ad esempio, l'equazione di Riccati in forma matriciale, la cui espressione analitica è Q+(A^T)P+P A-P B (R^(-1))(B^T)P==0

?FindRoot

Il comando seguente trova la matrice radice dell'equazione data:

FindRoot[Transpose[A] . P + P . A -P . B . Inverse[R] . Transpose[B].P ==-Q,
{P, {{10, 2}, {2,10}}}]

La funzione DSolve è anche in grado di individuare tutte le soluzioni razionali di un sistema di equazioni lineari con coefficienti razionali e può risolvere i sistemi lineari di equazioni differenziali con coefficienti costanti.
L'esempio che segue mostra la soluzione di un sistema di equazioni differenziali i cui coefficienti sono funzioni razionali e la cui soluzione è razionale.

DSolve[ 
{ u'[t]==(((6+2 t-3 (t^3)-(t^5)) u[t])/(t (-3-2 t+(t^2)) (-1+(t^3))))+(((5+t) v[t])/((-3-2 t+(t^2)) (-1+(t^3)))),
v'[t]==-((4 t (-3-2 t+(t^2)) u[t])/((5+t) (-1+(t^3))))+(((1+20 (t^2)+3 (t^3)) v[t])/((5+t) (-1+(t^3))))},{u, v}, t]//TraditionalForm

Nella seguente equazione algebrico-differenziale DSolve restituisce una soluzione generale con un solo parametro, sebbene sia un sistema di ordine due. Un grado di libertà viene rimosso per via dell'equazione algebrica che lega le variabili.

DSolve[ x'[t]==x[t]+2y[t]∧x[t]+y[t]==0 ,{x, y}, t]//TraditionalForm

In questo caso si ha la libertà di scegliere un solo valore iniziale con valore arbitrario:

DSolve[ x'[t]==x[t]+2y[t]∧x[t]+y[t]==0∧x[0]==1, {x, y},t]//TraditionalForm

Quello che segue è il valore iniziale implicato dal valore iniziale dell'altra variabile:

y[0] /. First[ % ]

?RSolve

Infine, si riportano alcuni esempi della nuova funzione RSolve, che risolve sistemi di equazioni alle differenze lineari e non lineari, equazioni algebriche alle differenze ed equazioni alle differenze con derivate parziali. Proprio come nel caso di equazioni differenziali, le equazioni alle differenze anche se appaiono semplici, potrebbero generare soluzioni molto complicate, e non ci sono funzioni abbastanza generali per esprimere tutte le soluzioni in forma chiusa. In molti casi, comunque, tali soluzioni possono essere trovate, come negli esempi che seguono.

Equazione alle differenze per i polinomi di Legendre.

RSolve[u[x]-(((3+2x)(d+c z)u[1+x])/(1+x))+(((2+x)u[2+x])/(1+x))==0,u,x]//TraditionalForm

Equazione (Subscript[y, k+1])=(k+1)(Subscript[y, k]) con una costante arbitraria (Subscript[c, 1]).

RSolve[y[k+1]==(k+1)y[k],y,k]//TraditionalForm

Equazione alle differenze (Subscript[y, k+1])=(k+1)(Subscript[y, k]) con l'equazione iniziale (Subscript[y, 0])=1.

RSolve[y[k+1]==(k+1)y[k] && y[0]==1,y,k]//TraditionalForm

Si risolve un'equazione alle differenze del secondo ordine con due condizioni al contorno.

s=RSolve[y[k+2]+2y[k+1]+y[k]==0 &&y[0]==1 && y[1]==2,y,k]

È possibile visualizzare graficamente la soluzione con un tipico diagramma stem (diagramma a rami). Si noti che s è stata determinata senza utilizzare la funzione TraditionalForm che ne avrebbe impedito il successivo utilizzo.

ListPlot[{Table[Evaluate[{k,y[k]/. First[s]}],{k,0,10}]},Filling->Axis,Background->LightGray]

Questo esempio mostra la soluzione dell'equazione lineare del secondo ordine con coefficienti variabili: (n^2)y(n)+((n^2)+1)y(n+1)+y(n+2)=0.

RSolve[ y[n+2]+((n^2)+1)y[n+1]+(n^2) y[n]==0,y,n]//TraditionalForm

Si risolve ora un sistema lineare di equazioni alle differenze e equazioni algebriche. Si noti che questo sistema ha solo una costante generale nella soluzione, poiché un grado di libertà è stato rimosso dall'equazione algebrica.

RSolve[x[k+1]==x[k]+2y[k]∧x[k]+y[k]==0,{x, y}, k]

Di seguito si riporta il problema del valore iniziale con un solo valore iniziale assegnato:

RSolve[x[k+1]==x[k]+2y[k]∧x[k]+y[k]==0∧x[0]==1,{x, y},k]

Questo è il valore iniziale di y:

y[0]/.First[%]

Le equazioni alle differenze parziali hanno diverse variabili indipendenti. In tali casi, la soluzione generale è ottenuta parametrizzando la funzione generale.

Segue un esempio di equazione alle differenze parziali lineare, con una funzione arbitraria generale C[1] [l -k].

RSolve[ u[k+1, l+1]== a u[k,l],u, {k,l}]

Alcune operazioni con le matrici

Nei paragrafi precedenti si è parlato genericamente di liste; in alcuni casi si è già introdotta l'interpretazione delle liste come vettori o matrici. Se una lista assume una forma particolare, ad esempio se si tratta di una lista di liste e tutte queste sono della stessa dimensione, allora si può attribuire il significato di matrice a tale struttura, e su di essa si possono eseguire le comuni operazioni appartenenti all'algebra delle matrici.
Come concetto generale, non bisogna mai dimenticare che per Mathematica una lista è una struttura dati, ancor prima di assumere significato di vettore o matrice. Questo significa che alcune operazioni possono essere svolte anche se dal punto di vista teorico non sono ammissibili. Ad esempio, sebbene l'operazione di divisione tra matrici non è definita, in Mathematica è possibile dividere due matrici. Tale operazione, come mostra l'esempio che segue, assume un significato solo dal punto di vista delle liste.

Clear[A,B,s];

A = {{1,-4,0},{3,-2,11},{3,6,2}};A//MatrixForm

B={{-3,7,4},{1,-1,2},{3,5,1}};B//MatrixForm

(A/B)//MatrixForm

In questo caso, seppure i due oggetti di tipo lista A e B possono essere considerati come matrici, Mathematica li considera come strutture dati sui quali l'utente ha chiesto di eseguire l'operazione di divisione. Sarà l'utente a dover intendere questa operazione come la creazione di una lista di liste i cui elementi sono dati dalla divisione degli elementi di posto omologo delle due liste A e B e non come l'operazione di divisione tra le due matrici A e B. Mathematica esegue sempre le operazioni aritmetiche su liste di forma compatibile, applicando l'operazione sui termini di posto omologo. Infatti, oltre alla divisione, anche le altre operazioni aritmetiche assumono lo stesso significato sulle liste. Ad esempio, la moltiplicazione eseguita con l'operatore * o equivalentemente con il carattere blank (lo spazio) assume il significato di moltiplicazione degli elementi di posto omologo e non di prodotto matriciale, il quale si ottiene con la funzione Dot, rappresentata in forma breve con il punto .

A*B//MatrixForm

A B//MatrixForm

Dot[A,B]//MatrixForm

A.B//MatrixForm

A-s//MatrixForm

A+{a,b,c}//MatrixForm

Ovviamente, se le due liste non hanno una forma compatibile viene restituito un messaggio di errore.

A*{1,2}

Mathematica mette a disposizione numerose funzioni per la manipolazione di matrici. La tabella che segue ne riporta un breve elenco.

Si noti che in Mathematica non c'è differenza tra un vettore riga ed un vettore colonna, in quanto le funzioni che operano su vettori e matrici automaticamente individuano la forma appropriata in base all'input fornito. Se si deve moltiplicare la matrice A a sinistra per un vettore riga ed a destra per un vettore colonna, basta scrivere:

{a,b,c}.A.{d,e,f}

Il prodotto scalare di due vettori viene eseguito dalla funzione Dot:

{a,b,c}.{d,e,f}

mentre la funzione Cross (oppure l’operatore ×) esegue il prodotto vettoriale:

u={a,b,c}

v={d,e,f}

w=Cross[u,v]

w=u\[Cross]v

Simplify[u.w]

Simplify[v.w]

La funzione Norm restituisce la norma di un vettore:

Norm[{a,b,c}]//TraditionalForm

Norm[{3,4,5}]

Più volte in precedenza si è utilizzata la visualizzazione in forma matriciale di una lista di liste, tramite la funzione MatrixForm. Bisogna prestare attenzione alle forme di visualizzazione disponibili in Mathematica, in quanto non devono mai essere utilizzare quando si assegna il valore ad una variabile. Si consideri il seguente esempio:

A =MatrixForm[{{-3,3,1},{0,6,11},{5,-4,9}}]

B=MatrixForm[{{15,7,-4},{3,5,4},{-1,2,4}}]

A.B

A+B

Inverse[A]

Il risultato è inaspettatamente privo di significato, ossia viene visualizzata l'operazione richiesta, ma non viene eseguito alcun calcolo. Il motivo è collegato al modo in cui si sono memorizzate le due matrici.

FullForm[A]

FullForm[B]

cioè esse sono oggetti di tipo MatrixForm e non List, dunque le operazioni sulle liste non sono applicabili. La MatrixForm ha significato unicamente per quanto attiene la visualizzazione del front end della lista in forma di matrice, ma non per la sua interpretazione dal punto di vista delle strutture dati. Infatti, MatrixForm funziona in generale su qualsiasi lista, anche se essa non rappresenta una matrice. Quella che segue è banalmente una lista ma non una matrice, ciò non toglie che sia possibile utilizzare la MatrixForm su di essa:

MatrixForm[{{1,2,3},{2},{0,3,-1},pippo}]

Pertanto, bisogna ricordarsi di non utilizzare mai la MatrixForm quando si definisce una matrice e la si assegna ad una variabile. È preferibile utilizzarla solo quando si chiede di visualizzare le matrici o il risultato di una operazione su di esse.

A ={{-3,3,1},{0,6,11},{5,-4,9}}

B={{15,7,-4},{3,5,4},{-1,2,4}}

A.B//MatrixForm

A+B//MatrixForm

Ritornando all'utilità della MatrixForm, quando si lavora con le matrici, se si desidera visualizzare sempre i risultati in forma tradizionale, si può impostare la variabile d'ambiente $Post come segue:

$Post = MatrixForm

Da questo punto in avanti, tutti gli output sotto forma di liste verranno rappresentati automaticamente come matrici.

A ={{-3,3,1},{0,6,11},{5,-4,9}}

Inverse[A]

Anche per le operazioni sulle matrici può tornare utile, alle volte, sfruttare il calcolo simbolico per visualizzare regole generali. Ad esempio, per mostrare come si ottiene il prodotto di due matrici si può procedere nel seguente modo.
Si creano due matrici simboliche usando la funzione Array e poi si esegue il loro prodotto.

A=Array[a,{2,2}]

B=Array[b,{2,2}]

A.B//MatrixForm

A+B//MatrixForm

Inverse[A]

Det[A]

A=Array[a,{4,4}]

Transpose[A]

La funzione MatrixPlot è molto utile per sintetizzare la struttura di una matrice mediante una  rappresentazione grafica dei suoi elementi.

M=(({{0, 0, 0, 0, 5, 2, 5, 4, 3, 5, 2, 0, 0}, {0, 0, 0, 0, 1, 1, 5, 5, 1, 2, 3, 0, 0}, {0, 0, 0, 0, 3, 1, 4, 2, 2, 4, 5, 0, 0}, {3, 1, 5, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5}, {4, 2, 4, 5, 0, 0, 0, 0, 0, 0, 0, 2, 3}, {5, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 3, 1}, {4, 2, 4, 3, 0, 0, 0, 0, 0, 0, 0, 3, 2}, {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2}}));

MatrixPlot[M]

Il risultato ottenuto in questo caso, evidenzia come la matrice M sia una matrice a blocchi, infatti le aree in bianco nel grafico indicano i blocchi con elementi nulli.  Se si aggiunge l'opzione Mesh, si evidenzia maggiormente la struttura di righe e colonne.

MatrixPlot[M,Mesh->True,MeshStyle->{RGBColor[0,0,1]}]

La MatrixPlot risulta maggiormente significativa per la visualizzazione di struttura per matrici molto grandi, come negli esempi che seguono.

$Post=. (* disattivo il MatrixForm automatico *)

Dimensions[M]

MatrixPlot[M]

Dimensions[M]

MatrixPlot[M]

Riducendo il numero di elementi da visualizzare è possibile anche evidenziare con diverse tonalità di grigio densità degli elementi non nulli.

MatrixPlot[M,MaxPlotPoints->20,ColorFunction->(GrayLevel[1.5-#1]&),ColorFunctionScaling->True]

Matrici sparse

Come ultimo argomento, esaminiamo la gestione ottimizzata per le matrici sparse, già brevemente introdotte in precedenza. Un elevato numero di problemi reali si affronta con modelli matematici che presentano matrici sparse, ossia matrici dove la maggior parte degli elementi sono nulli. La gestione di tali matrici in Mathematica è totalmente integrata con le altre funzionalità del sistema e permette di lavorare con array di qualsiasi dimensione. Questo significa che una volta definita una matrice con l'apposita funzione SparseArray si possono utilizzare tutte le altre funzioni.
Per creare una matrice o vettore sparso, bisogna usare la funzione SparseArray, la cui struttura più comune è la seguente: SparseArray[regole, dimensione]

?SparseArray

Il meccanismo delle regole permette di semplificare la creazione di particolari tipi di matrici. Infatti, per alcuni tipi di matrice, ad esempio quelle diagonali o triangolari, gli elementi non nulli sono disposti su particolari posizioni della matrice. Se, oltre alla posizione, anche il loro valore può essere determinato mediante una particolare funzione, allora conviene costruire una regola che a partire dagli indici di posizione i e j calcola automaticamente i valori delle entrate.
Ad esempio, per costruire la matrice identità basta scrivere:

ID=SparseArray[{i_,i_}->1,4]

Come si osserva dall'output, l'oggetto generato è di tipo SparseArray e non lista. Gli SparseArray vengono gestiti dalle funzioni native come una lista di liste. L'utente non deve farsi carico di specificare alcuna informazione nel trattare tali oggetti come se fossero una vera e propria matrice.

Transpose[ID]

Det[ID]

MatrixForm[ID]

MatrixForm[ID.ID]

MatrixForm[Inverse[ID]]

MatrixForm[Transpose[ID]]

Se si desidera convertire un oggetto di tipo SparseArray in una lista, si può utilizzare la funzione Normal.

Normal[ID]

Se si desidera conoscere le regole interne di una matrice sparsa, si può utilizzare la funzione ArrayRules.

ArrayRules[ID]

Si noti che, anche se esiste il comando IdentityMatrix che genera matrici identità di qualsiasi dimensione, se le dimensioni della matrice sono notevoli è comunque preferibile usare la sua rappresentazione nella forma SparseArray. 
Si calcola il numero di byte per memorizzare una matrice identità di dimensioni 1000×1000 in entrambi i modi.

ByteCount[IDsparsa]

ID= N[IdentityMatrix[1000]];

ByteCount[ID]

Per quanto concerne i tempi di calcolo, in alcuni casi non si registrano notevoli differenze tra le due forme di memorizzazione.

Timing[Det[ID];]

Timing[Det[IDsparsa];]

Una possibile risposta è che la forma sparsa, sebbene occupi meno memoria, conserva delle regole e non dei numeri, per cui eseguire calcoli complessi come il calcolo di una inversa, può richiedere maggior tempo rispetto alla equivalente forma completa. Comunque vi sono esempi in cui il calcolo con le matrici sparse risulta vantaggioso anche rispetto ai tempi di elaborazione.
Se si deve costruire una matrice sparsa con elementi non nulli disposti su posizione non esprimibili in maniera diretta tramite un'unica regola, allora si può utilizzare una regola per ciascun elemento o una regola per quegli elementi che rientrano in un determinato criterio e delle singole regole per gli altri.

M=SparseArray[{{1,2}->1,{2,3}->4,{3,1}->-2,{3,3}->2}]

MatrixForm[M]

Costruzione di una matrice con elementi non nulli sulla diagonale principale ed un altro elemento non nullo nella posizione (2,5).

SparseArray[{{i_,i_}->2i+1,{2,5}->-1},5]

MatrixForm[%]

L'esempio che segue mostra come costruire una matrice tridiagonale, tralasciando l’approfondimento della forma delle regole indicate (/; pattern test e :> rule delayed).

SparseArray[{{i_,j_}/;Abs[i-j]≤1:>RandomInteger[{1,10}]},{5,5}]

MatrixForm[%]

Si costruiscono due matrici triangolari, superiore ed inferiore, rispettivamente:

SparseArray[{{i_,j_}/;i-j≤0:>RandomInteger[{1,10}]},{5,5}]

MatrixForm[%]

SparseArray[{{i_,j_}/;j-i≤0:>RandomInteger[{1,10}]},{5,5}]

MatrixForm[%]

Come ultimo esempio sulle matrici sparse, si riporta un ulteriore caso in cui la forma sparsa introdotta con SparseArray permette ora di eseguire calcoli praticamente impossibili con la equivalente forma completa. Si costruisce una matrice con (10^12) elementi.

A=SparseArray[{{i_,j_}/;Abs[i-j]≤1:>RandomReal[]},{n,n}]

In questo caso non si può creare la forma completa in quanto non vi è memoria sufficiente sul computer. Si effettueranno solo i calcoli dei byte impiegati da entrambe le forme per verificare la convenienza della forma sparsa. La matrice in forma completa deve memorizzare (10^6)×(10^6) numeri reali generati casualmente con la RandomReal[], ciascuno dei quali richiede 24 byte di memoria (nella versione 8 di Mathematica).

$Version

ByteCount[RandomReal[]]

Pertanto, il numero totale di byte necessari per salvare tutti gli elementi, compresi quelli nulli, è dato dal seguente calcolo:

MemoriaCompleta = N[ (10^6) (10^6) 24 ] Byte

mentre la matrice sparsa occupa soltanto poco più di 40 MegaByte:

MemoriaSparsa = N[ ByteCount[ A ] ] Byte

In questo caso, oltre al vantaggio di ridurre la memoria, si ottiene anche quello di poter eseguire calcoli su matrici così grandi. 
Si calcola ora un sistema lineare la cui matrice dei coefficienti è quella creata in precedenza, ed il vettore dei termini noti è creato con (10^6) numeri reali casuali. La velocità ottenuta è anche dovuta alla funzione LinearSolve che è in grado di applicare algoritmi ottimizzati per le matrici sparse.

b=RandomReal[{0,1},n];

Dimensions[A]

Dimensions[b]

Timing[ LinearSolve[ A, b ]; ]

Si è appena risolto un sistema di 1.000.000 equazioni in 1.000.000 di incognite, con una matrice dei coefficienti tridiagonale! In questo caso anche il prodotto di matrici sparse è sorprendentemente veloce:

Timing[ A.A;]

Se si dispone di una matrice in forma completa e la si vuole convertire nella forma sparsa, si può ancora utilizzare SparseArray:

A={{0,2,1,0},{9,-3,0,2},{0,-3,0,1},{0,0,2,2}}

MatrixForm[A]

B=SparseArray[A]

MatrixForm[B]

A-B

Infine, si segnala che mediante SparseArray è anche possibile creare matrici non necessariamente sparse, dove una parte delle entrate è comunque sempre uguale ad un certo valore.

A=SparseArray[{{2,3}->1, {3,2}->-1,{3,3}->3 },3,x]

MatrixForm[A]

A=SparseArray[{{2,3}->1, {3,2}->-1,{3,3}->3 },3,-10]

MatrixForm[A]

Cenni di programmazione

Mathematica è anche un linguaggio di programmazione. Lo stile di programmazione più vicino alla logica interna di Mathematica è quello funzionale.

Set e SetDelayed

Spesso, quando si assegna un valore ad una variabile di memoria, si parla indifferentemente di assegnazione, definizione o creazione di variabili. In realtà, il vero significato di ciascuna di tali operazioni è la creazione di una regola di sostituzione, che viene poi elaborata dal kernel al momento opportuno, in generale quando la variabile in questione viene riutilizzata in un'espressione successiva alla sua creazione. 
Da questo punto di vista Mathematica può essere considerato come un sistema basato esclusivamente sulle regole, in parte definite internamente dal sistema stesso, in parte create dall'utente. Scrivere l'espressione a=0 implica la creazione di una regola che sostituisce tutte le occorrenze del simbolo a con il valore numerico 0. Sebbene, in questo caso, si può dire che il simbolo a assume il significato di variabile di memoria (o brevemente variabile), l'interpretazione dell'operazione a=0 come "la creazione di una regola di sostituzione associata al simbolo a" permette di comprendere meglio la grande differenza tra Mathematica ed altri linguaggi. Infatti, una regola di sostituzione può prevedere l'utilizzo di qualsiasi espressione, non necessariamente un valore numerico.

a = Integrate[Sin[x],{x,-2π,π}]

b = Integrate[Sin[x],x]

??a

??b

In questo caso si è definita una coppia di regole, associate con i simboli a e b; la prima comporta un risultato di tipo numerico, la seconda un risultato di tipo simbolico. Infatti, b=Integrate[Sin[x],x] può essere interpretata come una funzione in quanto il risultato finale, b=-Cos[x], è un'espressione dipendente dalla variabile x, al momento non definita.

a+b

Questo meccanismo permette di definire funzioni, anche molto complesse, ossia regole di sostituzione che vanno ad aggiungersi a quelle native del sistema.
Per avere una buona padronanza con la creazione e l'utilizzo di regole è opportuno comprendere tre aspetti principali: come viene definita, quando sarà applicata, dove è memorizzata. Il primo aspetto, il modo in cui si definisce una regola, implica gli altri due. Per definire una variabile, ossia associargli una regola di sostituzione, vi sono due istruzioni: Set e SetDelayed. In precedenza si è già visto l'utilizzo dell'operatore usuale Set, indicato con =, per l'assegnazione di un valore o genericamente un'espressione ad una variabile. Si descrive ora un altro operatore di assegnazione molto utilizzato, il SetDelayed, indicato con :=. Si vuole definire una variabile a che assume valori in funzione di un'altra variabile x, mediante una relazione qualsiasi, ad esempio tramite la funzione trigonometrica Sin[x]. Si esegue due volte la stessa operazione, utilizzando i due operatori sopra richiamati.

a = Sin[x]

L'osservazione da fare è che nel primo caso il kernel ha risposto replicando in output la stessa espressione data in input, perché non essendo definito un valore per x la funzione Sin non viene computata; nel secondo caso, invece, non viene restituito alcun output. Da ciò si deduce che, nel primo caso, è stata eseguita una computazione, il cui risultato è Sin[x]. Nel secondo caso, non avendo ottenuto alcun output, si può ipotizzare che il kernel abbia solo registrato la regola indicata, senza effettuare alcuna operazione. In questo esempio le due variabili a e b sono praticamente identiche, come facilmente si verifica:

a

b

a+b

Anche nel caso in cui si assegni successivamente un valore alla x, le due variabili a e b si comportano allo stesso modo:

x=π

a

b

TableForm[
Table[{N[a],N[b]}, {x,1,5}],
TableHeadings->{None,{"a","b"}},
TableAlignments->{Decimal}]

Qual è, dunque, la differenza tra i due operatori? 
In questo esempio non vi sono reali differenze nel modo in cui operano a e b dopo l'assegnazione. Si riporta, allora, un caso analogo, in cui tale differenza viene evidenziata.
Si ripete l'assegnazione di a e b, ora che la variabile x assume un particolare valore.

a=Sin[x]

a

b

Sembrerebbe che non vi siano ancora differenze. Si provi ora a cambiare il valore della x:

x = (π/2)

a

b

TableForm[
Table[{N[a],N[b]}, {x,1,5}],
TableHeadings->{None,{"a","b"}},
TableAlignments->{Decimal}]

In definitiva, l'operatore Set valuta il suo lato destro immediatamente e memorizza il risultato in a, mentre SetDelayed memorizza il suo lato destro senza valutarlo immediatamente, operazione che verrà eseguita solo successivamente, ogni qual volta verrà chiesto dall'utente di utilizzare la variabile b. In generale, dunque, l'assegnazione di una regola con SetDelayed, non tiene conto del valore delle variabili e dei risultati delle operazioni indicate nella definizione, al momento della sua creazione. Tali elementi avranno il loro effetto solo dopo, quando la regola viene utilizzata.

Per una conferma, basta vedere come il sistema ha memorizzato le due variabili:

??a

??b

Per visualizzare solo le definizioni associate ad un simbolo, si può anche utilizzare la funzione Definition.

Definition[a]

Definition[b]

Si riportano ancora due esempi che mostrano in maniera più marcata la differenza tra i due operatori di assegnazione sopra visti, evidenziando anche che la loro differenza non dipende solo da eventuali variabili preesistenti, ma anche dalle operazioni specificate a destra dell'operatore di assegnazione.

a =Expand[(((1+x))^2)]

Le due variabili memorizzano espressioni diverse:

Definition[a]

Definition[b]

Infatti, modificando il valore della x, esse si comportano in maniera differente:

x = (z+1)

a

b

TableForm[
Table[{a,b}, {x,z+1,z+5}],
TableHeadings->{None,{"a","b"}},
TableAlignments->{Left}]

Altro esempio:

a=RandomInteger[{0,100}]

b:=RandomInteger[{0,100}]

Il valore di a è calcolato in maniera casuale ma solo la prima volta, perché si è utilizzata l'assegnazione immediata, mentre b assume un valore casuale ogni volta che viene richiamata.

TableForm[
Table[{a,b}, {x,z+1,z+5}],
TableHeadings->{None,{"a","b"}},
TableAlignments->{Center}]

Definizione di funzioni

La definizione di funzione risulta utile in moltissimi casi, infatti è una delle caratteristiche di Mathematica maggiormente utilizzate.

Per utilizzare la funzione così definita, d'ora in poi bisognerà chiamarla passandogli un argomento, altrimenti non vi sarà alcun output:

f

?f

f[3]

f[(3/2)]

TableForm[
Table[{x,f[x]}, {x,-2,2}],
TableHeadings->{None,{"x","f[x]"}},
TableAlignments->{Right}]

La f così definita può essere richiamata con un qualsiasi tipo di argomento, non necessariamente numerico. Questo implica che è possibile usare la definizione di f[x] anche per calcoli simbolici o per visualizzarne il grafico.

f[t]

Plot[f[a],{a,-2 π,2 π},Background->LightGray]

D[f[x],x]

Integrate[f[x],x]

Si noti che, avendo utilizzato il SetDelayed, la variabile x che compare nella definizione della funzione non viene a sovrapporsi con eventuali valori della x assegnati in precedenza o che si assegneranno successivamente; in tali casi la variabile assume il senso di variabile locale.

Viene assegnato un valore alla x:

x=0

Viene definita la funzione f:

Il valore di f non dipende dal valore di x definito in precedenza.

?f

Anche le modifiche di x successive alla definizione di f non ne influenzano il comportamento.

x=10

?f

f[0]

f[15]

f[y]

Si noti, però, la differenza nel momento in cui la funzione f viene chiamata passandogli come argomento la variabile x.

f[x]

Infatti, essendo x definita come x=10, prima di eseguire il corpo della funzione f viene valutato il valore di x. Per comprendere meglio come viene elaborato tale input, si può utilizzare la funzione Trace, che mostra i passaggi del kernel nell'applicare tutte le regole definite dall'utente.

Trace[f[x]]

Dunque, scrivere f[x] equivale a f[10].

Ritornando alla forma usata per definire f, si sarà notato l'uso del trattino di sottolineatura _ (chiamato anche Blank[]) al fianco del nome dell'argomento, nella parte sinistra dell'assegnazione (lhs). Il suo significato è molto articolato e, per il momento, è sufficiente interpretarlo come "il modello o pattern di una qualsiasi espressione accettata da Mathematica". In altre parole nelle definizioni del tipo f[x_]:=CorpoDellaFunzione, esso indica che la funzione f sarà eseguita quando verrà chiamata con un solo argomento (perché c'è un solo parametro x) che può assumere la forma di una qualsiasi espressione sintatticamente corretta. Più in avanti si avrà modo di approfondire tale argomento, che rientra nel meccanismo del pattern matching. È importante ricordarsi che il simbolo _ deve essere utilizzato solo per la definizione della funzione e non quando, successivamente, si usa la funzione stessa.
Il concetto di qualsiasi espressione è molto forte, in quanto davvero è possibile passare qualunque oggetto ad una funzione, come mostrano gli esempi che seguono.

f[∫Tan[x]\[DifferentialD]x]

f[N[ReplaceAll[Simplify[Expand[(((2+x))^4) (((1+x))^4) (((3+x))^3)]],x-> π]]]

f[Replace[x,Solve[(x^2)+3x-2==0]]]

f[{1,2,3,4,5}]

Si supponga ora di voler definire la funzione f con tre differenti valori. Una possibilità offerta da Mathematica è quella di utilizzare l'indicizzazione della variabile f come segue:

In tal modo è possibile far riferimento ad una qualsiasi delle definizioni della funzione f indicando l'indice opportuno. Se, ad esempio, si desidera eseguire il grafico delle tre definizioni si può scrivere:

Table[Plot[f[i][x],{x,-2π,2π}],{i,3}]

Oppure, si potrebbe scegliere a caso una delle tre funzioni e calcolarne il valore in un determinato punto.

f[RandomInteger[{1,3}]][(π/2)]

Come si osserva facilmente, dunque, si ha un'espressione la cui head non è un atomo, bensì ancora un'espressione.

??f

Si riporta ora un ultimo esempio relativo alla definizione di funzioni per casi particolari. Si vuole definire una funzione ricorsiva. Ad esempio una funzione del tempo che, ad ogni istante, calcola la somma dei valori assunti dalla funzione negli istanti precedenti più un certo valore funzione dell'istante corrente. Si può procedere come segue. 
Per effettuare prove su un numero elevato di chiamate ricorsive, si rimuove il limite impostato dalla variabile $RecursionLimit sul numero di chiamate ricorsive.

{R[10],R[100],R[200],R[5000]}

Si osservi bene cosa accade ad ogni chiamata della funzione R[t]:

Trace[R[3]]

Come si vede con l'aiuto della Trace, il calcolo di ciascun valore di R viene eseguito secondo la formula ricorsiva assegnata. L'effetto poco gradevole di tale metodo è che non vengono memorizzati i valori precedenti di R che potrebbero servire per essere visualizzati o per eventuali chiamate successive della R, senza volerli ricomputare. Per ovviare a questo problema, si potrebbe aggiungere qualche altro comando, ad esempio:

valori=Table[R[i],{i,0,10}]

ListPlot[valori,Joined->True]

Si sono salvati i valori precedenti nella variabile valori, ma non si è risolto il problema di evitare che essi vengano computati nelle successive chiamate di R[t]. Le due linee di input che seguono mostrano come tra una chiamata e l'altra non vi sia una differenza significative di tempo, in quanto in entrambe vengono ricalcolati tutti i valori di R:

Timing[R[10000]]

Timing[R[10000]]

Esiste un modo più semplice ed efficiente per ottenere il risultato desiderato, cioè memorizzare i valori precedenti in maniera da poterli visualizzare e riutilizzare nei calcoli successivi risparmiando tempo. Si ridefinisce la funzione R nel seguente modo.

?R

Si osservi che è possibile indicare la funzione R[t] a sinistra di due assegnazioni nidificate. Nella prima R[t_] definisce la regola generale, mentre nella seconda R[t] memorizza il valore ad ogni chiamata con un t fissato. Si provi a chiedere il calcolo di R[10] e successivamente si osservi cosa è cambiato nella definizione di R:

R[10]

??R

Come anticipato, ad ogni chiamata di R con un t fissato, viene calcolato R[t] e contemporaneamente vengono memorizzati i risultati per tutti i valori di k<t. Per riutilizzare tali valori basta richiamare R[k], con k<t, senza bisogno di salvarli in una variabili di appoggio.

ListPlot[Table[R[i],{i,10}],Joined->True]

Altro vantaggio evidente è che, una volta calcolato R[t] per un qualsiasi valore di t, le definizioni di R già note vengono utilizzate anche per il calcolo di R[w] con w>t. La conferma viene data sia dalla Trace che dalla Timing:

Trace[R[10]]

Trace[R[11]]

$RecursionLimit=1000;

Timing[R[1000]]

Timing[R[1000]]

Trace[R[1000]]

Si definisca ora una funzione per il calcolo delle radici n-sime di un numero complesso z:

Bisogna creare una funzione che richiede in input due argomenti, il numero complesso e l'indice della radice. Tutto quello che bisogna fare è specificare la sua forma nella parte a sinistra dell'operatore := e l'algoritmo per il calcolo nella parte destra dell'operatore, come nella seguente espressione.

RadiciComplesse[(Sqrt[2])-(Sqrt[2])I,4]//TraditionalForm

Come ultima osservazione, si noti che anche per le funzioni definite dall'utente si ha disposizione il meccanismo del completamento automatico del nome e dell'espressione. Per quanto riguarda il primo, è immediatamente disponibile non appena si è definita la funzione. Infatti, si provi a digitare solo le prime tre lettere del nome RadiciComplesse, e poi si digiti Ctrl [k]. Apparirà la finestra che indica tutte le funzioni il cui nome comincia con Rad, come in figura:

Per quanto riguarda, invece, il completamento automatico dell'espressione, bisogna definire un ulteriore elemento per la funzione RadiciComplesse, ossia il suo messaggio di help in linea. Per fare ciò si utilizza la seguente espressione:

RadiciComplesse::usage="RadiciComplesse[c, n] calcola le radici n-sime del numero complesso c."

In tal modo si è creato il messaggio di help che compare quando si utilizza il simbolo ?

??RadiciComplesse

e, contemporaneamente, si è creata l'espressione tipo che compare con il completamento automatico dato dalla sequenza ⇧+Ctrl [k]. Si provi con la scritta seguente:

comparirà

La Grafica

Come per ogni altro oggetto di Mathematica, il grafico ha un significato ed una forma per l’utente, ed un altro significato ed un’altra corrispondente forma per il sistema. A tal proposito è bene distinguere la generazione dell’oggetto grafico (e quindi della sua forma interna) dalla visualizzazione del grafico stesso. 
Quindi si possono distinguere due classi di istruzioni per la grafica: quelle che sono in grado di generare un oggetto grafico e di mostrarlo (ad esempio Plot, ParametricPlot, ListPlot, etc.), e quelle che sono in grado semplicemente di generarlo (ad esempio Graphics, Graphics3D, etc.) e che necessitano poi di una funzione di supporto, chiamata Show, per mostrare il grafico.

Grafici 2D di funzioni

Plot e ListPlot

Per effettuare il grafico di una funzione espressa in forma analitica bisogna utilizzare la funzione Plot. Ad esempio, se si vuole visualizzare il grafico della funzione Sin[x], bisogna indicare al sistema sia la funzione, sia l’intervallo da utilizzare sull’asse delle x. Come accennato nel precedente paragrafo, questa informazione è indispensabile al sistema per poter eseguire il grafico, in quanto la funzione seno, essendo definita in ℝ, può essere disegnata da -∞ a + ∞. Dunque, il secondo argomento della Plot sarà una lista del tipo {x, xmin, xmax}.

Plot[Sin[x],{x,-π,π}]

Plot[Sin[x],{x,-π,π}];

Cosa è cambiato rispetto a prima ?
Manca la cella di output, infatti è stato semplicemente generato il grafico ma non è comparsa alcuna cella con la label Out[n]:=. Il trucco utilizzato è il punto e virgola alla fine della linea di input, che ha l'effetto di inibire la visualizzazione dell'output. La cella contenente il grafico non viene etichettata come cella di output e questo significa che non lo è (se lo fosse stato non sarebbe comparsa a causa del ;). In conclusione, la visualizzazione del grafico può essere considerata come un effetto collaterale (side effect), che eventualmente può anche essere eliminato, e non come un risultato della funzione Plot. 
Ritornando alla risposta ottenuta in precedenza, avendo specificato che la variabile indipendente x varia da -π a π, il sistema determina 25 punti equidistanti tra loro, nell'intervallo [-π,π], per i quali calcola il valore dell'immagine Sin[(Subscript[x, i])] effettuando così la discretizzazione della funzione continua Sin[x]. Successivamente, esegue alcuni algoritmi di interpolazione per calcolare ulteriori valori della funzione al di fuori dei 25 già ottenuti al primo passo. A questo punto il sistema dispone degli opportuni elementi per eseguire il grafico. L'oggetto grafico generato ha una head di tipo Graphics seguita da una tabella con coppie ordinate ((Subscript[x, i]), Sin[(Subscript[x, i])]) ed alcune direttive che impostano l'aspetto del grafico. Trattandosi di una Plot, una delle istruzioni che generano e mostrano i grafici, il sistema prosegue automaticamente con la visualizzazione del grafico. Per avere conferma di cosa viene realmente computato quando si chiama la funzione Plot, è sufficiente chiedere la FullForm o la InputForm di uno degli output precedenti:

Pl

Grafica 3D

?Plot3D

Analogamente alla grafica bidimensionale, è possibile utilizzare gli stessi comandi nella versione 3D. Ad esempio:

Plot3D[Sin[x y],{x,-π,π},{y,-π,π},Axes->None]

?Manipulate

E’ anche possibile variare dinamicamente il grafico con la funzione Manipulate.

Manipulate[Plot3D[Sin[a x y],{x,-π,π},{y,-π,π},Axes->None],{{a,2,"Parametro"},1,9}]

?ListPlot3D

InputForm[%]

Essendo l'oggetto Graphics una normale espressione di Mathematica, i grafici possono essere manipolati esattamente come tutte le altre espressioni. Tra le altre cose, essi possono essere salvati in una variabile per poi essere rivisualizzati all'occorrenza, ad esempio qualora si vogliano generare più grafici e poi mostrarli contemporaneamente su di uno stesso piano cartesiano. In tale caso si può procedere come segue.

gr1=Plot[Sin[x],{x,-π,π}]

gr2=Plot[Cos[x],{x,-2 π,2 π}]

Show[gr1,gr2]

Nel caso in cui si conoscano le espressioni analitiche di più funzioni, si può richiederne il grafico su di un unico piano cartesiano senza dover prima generare i singoli grafici separatamente e poi unirli con la Show.

Plot[{Sin[x],Cos[x],Tan[x]},{x,-π,π},PlotRange->{Automatic,{-3,3}}]

Plot[{Sin[x]},{x,-π,π},Ticks->{{{-π,"a"},{-(π/2),"b"},{(π/2),"c"},{π,"d"}},None}]

Nel caso precedente, ad esempio, si può inserire un colore nello sfondo del grafico, mediante il comando Show e l' uso dell' opzione Background -> DirettivaColore.

Show[gr1,gr2,Background->GrayLevel[0.9]]

Fino ad ora si è visto come realizzare grafici di funzioni di cui siano note le espressioni analitiche, ma non sempre queste ultime sono disponibili; nella pratica capita spesso di conoscere solo una sequenza di punti, ossia coppie ordinate ((Subscript[x, i]),(Subscript[y, i])), e si ha l'esigenza di conoscere l'andamento di una ipotetica funzione che può assumere quei valori. In questi casi non si può utilizzare la Plot per eseguire il grafico, ma si ha a disposizione un'altra funzione, la ListPlot, che permette di visualizzare nel piano cartesiano i punti assegnati.

Si supponga di avere a disposizione la tabella seguente con 20 coppie ordinate:

punti=RandomInteger[{-10,10},{20,2}];

Si richiede al sistema di disegnare tali punti sul piano cartesiano utilizzando la ListPlot:

ListPlot[punti]

Osservando bene il grafico si comprende che alcuni punti possono risultare difficilmente visibili, soprattutto quelli che hanno una coordinata nulla e quindi giacciono su un asse oppure quelli che capitano dove compaiono i numeri sugli assi. L'esempio seguente evidenzia maggiormente tale problema, ed introduce l'uso delle opzioni per le funzioni grafiche, utili a modificare l'aspetto dei grafici per una loro migliore comprensione.

ListPlot[punti]

Risulta che solo due punti su cinque, sono visibili. Se si desidera un grafico maggiormente esplicativo, magari con i punti più grandi o di un determinato colore, o magari vedere una linea che li congiunge, si deve operare con le opzioni.

Dunque, per rendere il grafico più esplicito si può, ad esempio, tracciare una linea di colore rosso che congiunge i punti utilizzando le seguenti opzioni:

punti

ListPlot[punti,PlotStyle->RGBColor[1,0,0],Joined->True]

oppure aumentare la dimensione dei singoli punti, in maniera da renderli più visibili:

ListPlot[punti,PlotStyle->PointSize[0.03]]

Come fare se si desidera impostare contemporaneamente il colore rosso e la taglia per i singoli punti? Con una lista di opzioni:

ListPlot[punti,PlotStyle->{RGBColor[1,0,0],PointSize[0.03]}]

Approfondimenti su alcune opzioni della funzione Plot

AspectRatio

AspectRatio indica il rapporto tra l'altezza e la larghezza del grafico.

Plot[{Sin[x],Cos[x]},{x,-π,π}]

Plot[{Sin[x],Cos[x]},{x,-π,π},AspectRatio->1]

Plot[{Sin[x],Cos[x]},{x,-π,π},AspectRatio->0.1]

PlotRange

Si può specificare qual è l'intervallo desiderato per la y tramite l'opzione PlotRange.

Per vedere solo i valori positivi della funzione Sin[x]:

Plot[Sin[x],{x,-2 π,2 π},PlotRange->{0,1}]

Per vedere solo i valori compresi nell'intervallo [-0.5,0.5]:

Plot[Sin[x],{x,0,5 π},PlotRange->{-0.5,0.5}]

Plot[Tan[x],{x,0,2 π},PlotRange->All]
(* Tutti i punti. Se la funzione assume valori infiniti il grafico è troncato *)

Plot[{Sin[x],Cos[x],Tan[x]},{x,-π,π},PlotRange->{Automatic,{-2,2}}]
(* Range x automatico; -2≤y≤2 *)

PlotStyle

Plot[{f[x],f'[x]},{x,-2 π,2 π},PlotStyle->{{RGBColor[0,0,1]},{RGBColor[1,0,0]}}]
(* f(x) blu, f'(x) rosso *)

In questo esempio la prima sottolista contiene le direttive per la prima curva (spessore), la seconda quelle per la seconda curva (colore rosso).

Plot[{x ((Sin[x])^2),x ((Cos[x])^2)},{x,-2 π,2 π},PlotStyle->{{Thickness[0.015`]},{RGBColor[1,0,0]}}]

Si supponga, invece, di voler eseguire il grafico con una serie di direttive di stile da applicare ugualmente ad entrambe le funzioni, ad esempio colore rosso (RGBColor[1,0,0]) e spessore 0.01 (Thickness[0.01]). L’espressione è (attenzione alle doppie parentesi graffe):

Plot[{x ((Sin[x])^2),x ((Cos[x])^2)},{x,-2 π,2 π},PlotStyle->{{Thickness[0.01`],RGBColor[1,0,0]}}]

Addirittura, volendo indicare alcune direttive solo per una curva si può utilizzare anche la seguente espressione:

Plot[{f[x],f'[x]},{x,-2 π,2 π},PlotStyle->{{},{RGBColor[1,0,0],Dashing[{0.01}]}}]

Ovviamente, quanto sopra detto non è necessario nel caso di un grafico di una sola funzione, per la quale possono essere impartite direttive di stile in un’unica lista di argomenti della PlotStyle.

Plot[(x^2) ((Cos[x])^3),{x,-2 π,2 π},PlotStyle->{Thickness[0.006],RGBColor[1,0,0],Dashing[{0.02}]}]

PlotLabel e PlotLegend

Se si desidera aggiungere un titolo al grafico si può utilizzare l'opzione PlotLabel, che aggiunge un'etichetta al riquadro del grafico. Il grafico seguente mostra 3 funzioni disegnate con tre tonalità diverse di grigio ed un titolo che riporta le espressioni delle funzioni stesse.

Plot[{(x^2) Sin[x],(x^2) Cos[x],x ((Sin[x])^2)},{x,-2 π,2 π},Ticks->False,PlotStyle->{GrayLevel[0],GrayLevel[0.5`],GrayLevel[0.7`]},PlotLabel->{(x^2) Sin[x],(x^2) Cos[x],x ((Sin[x])^2)}]

L'opzione PlotLabel non è molto flessibile, in quanto non permette di spostare la posizione della label o di indicare diverse stringhe di testo a seconda delle varie funzioni presenti in un grafico. Una evoluzione della PlotLabel viene fornita dalla PlotLegend, implementata nel pacchetto aggiuntivo PlotLegends. Si mostrano di seguito alcuni esempi.

<<"PlotLegends`" (* carico il pacchetto *)

Plot[{(x^2) Sin[x],(x^2) Cos[x],x ((Sin[x])^2)},{x,-2 π,2 π},Ticks->False,PlotStyle->{GrayLevel[0],GrayLevel[0.5`],GrayLevel[0.7`]},FormatType->TraditionalForm,PlotLegend->{(x^2) Sin[x],(x^2) Cos[x],x ((Sin[x])^2)}]

La legenda può essere spostata e modificata mediante l'uso di specifiche opzioni, quali LegendPosition, LegendSize, LegendShadow, LegendOrientation, LegendLabel, LegendBorder, LegendBackground.

Plot[{(x^2) Sin[x],(x^2) Cos[x],x ((Sin[x])^2)},{x,-2 π,2 π},Ticks->False,PlotStyle->{GrayLevel[0],GrayLevel[0.5],GrayLevel[0.7]},FormatType->TraditionalForm,BaseStyle->{FontFamily->"Arial",FontSize->9},PlotLegend->{(x^2) Sin[x],(x^2) Cos[x],x ((Sin[x])^2)},LegendPosition->{1,-1},LegendSize->{0.7,1},LegendLabel->"Funzioni",LegendBackground->GrayLevel[0.9]]

PlotPoints, MaxRecursion

La PlotPoints permette di indicare alla Plot quanti punti campione devono essere presi nell'intervallo specificato per la variabile indipendente. L'opzione PlotPoints interviene sul numero di punti campione e dunque determina una maggiore o minore precisione del grafico. Il valore di default della PlotPoints è 25, ed alcune volte può capitare che con soli 25 punti l'algoritmo di interpolazione restituisca valori eccessivamente approssimati della funzione. In tali casi è possibile chiedere una maggiore precisione, aumentando il numero dei punti campione, mediante la PlotPoints. Nel seguente esempio il calcolo dei punti interpolanti evidentemente comporta un errore di approssimazione in un intorno di x=-60.

Plot[Cos[x],{x,-20 π,20 π}]

Ovviamente, se si cambia il Range di osservazione, l'errore scompare mostrando il vero comportamento della funzione per x∈{-60,-50}.

Plot[Cos[x],{x,-60,-50}]

In generale, prima di affidarsi completamente a valutazioni sulla funzione in base al grafico ottenuto, bisogna sempre tenere presente possibili approssimazioni dovute alla visualizzazione del grafico. Seguono alcuni esempi.

Plot[Sin[(x^2)+2 x-3],{x,-2 π,2 π},MaxRecursion->0]

Plot[Sin[(x^2)+2 x-3],{x,-2 π,2 π},MaxRecursion->15,PlotPoints->4]

Filling

Plot[Cos[x],{x,0,2},Filling->Axis]