Computing quantistico – 2

Questi i principi:
No-cloning: l’informazione quantistica non può essere copiata con fedeltà assoluta, e quindi neanche letta con fedeltà assoluta. (William Wootters, 1982).
L’informazione quantistica può invece essere trasferita con fedeltà assoluta, a patto che l’originale venga distrutto nel processo. Il teletrasporto quantistico è stato
ottenuto per la prima volta da Nielsen, Klinn e LaFlamme nel 1998.

Ogni misura compiuta su di un sistema quantistico distrugge la maggior parte dell’informazione, lasciandolo in uno stato base. L’informazione distrutta non può essere
recuperata. Ciò è una derivazione diretta dai postulati della meccanica quantistica (PMQ).

Anche se in qualche caso è possibile conoscere esattamente in che stato base si troverà il sistema dopo una misura, il più delle volte avremo solo previsioni probabilistiche.
Anche questo deriva direttamente dai PMQ.
Alcune osservabili non possono avere simultaneamente valori definiti con precisione, per il principio di indeterminazione di Heisenberg. Ciò ci impedisce sia di stabilire
con esattezza le condizioni iniziali prima del calcolo, sia di leggere i risultati con precisione.

L’informazione quantistica può essere codificata, e solitamente lo è, tramite correlazioni non-locali tra parti differenti di un sistema fisico.
In pratica, si utilizza l’entanglement.

Un qubit fisico può essere realizzato tramite trappole di ioni, punti quantistici o spin atomici nei semiconduttori, superconduttori, e fotoni.
In alternativa vi è l’approccio topologico, prescelto ad esempio da Microsoft per realizzare un computer quantistico e basato le proprietà dei gas bidimensionali di elettroni, che se
realizzato consentirebbe una maggiore resilienza ai fenomeni di decoerenza o di disturbo che affliggono i primi.

La sfera di Bloch è una rappresentazione di un qubit, il mattone dei computer quantistici
Il computing quantistico è lo studio di un modello di computazione non-classico. Mentre i modelli tradizionali come la macchina di Turing o il lambda calcolo si basano su rappresentazioni “classiche” della memoria computazionale,
una computazione quantistica può trasformare la memoria in una sovrapposizione quantistica dei possibili stati classici.Un computer quantistico (o quantico) è un dispositivo che può effettuare tali computazioni.
Nel 1994, Peter Shor pubblicò un algoritmo quantistico che fattorizza gli interi in tempo polinomiale.Questo fu una svolta epocale nella materia:
un importante metodo di crittografia asimmetrica noto come RSA si basa sulla credenza che la fattorizzazione degli interi sia difficile dal punto di vista computazionale. L’esistenza di algoritmo quantistico in tempo polinomiale dimostra che
uno dei protocolli crittografici più usati al mondo è vulnerabile a un computer quantistico.

Al 24 ottobre 2019, Google affermò ufficialmente che un computer quantistico completò un calcolo da 10000 anni in 200 secondi, ma in risposta un ricercatore prominente dichiarò che una rivoluzione del computer quantistico equivalente
a quella del computer classico richiederà “immensa ingegneria, e probabilmente anche ulteriori intuizioni.” C’è una quantità crescente di investimenti nel computing quantistico da governi, aziende già avviate e start up.
La ricerca accademica e industriale è anche incentrata sull’applicazione di dispositivi di media scala e la dimostrazione della supremazia quantistica[10] insieme allo scopo a lungo termine di costruire e usare un computer quantistico potente
e privo di errori.
Il campo del computing quantistico è strettamente correlato all’informatica quantistica, che comprende anche la crittografia quantistica e la comunicazione quantistica.

Nella maggior parte dei modelli di computazione classica, il computer ha accesso alla memoria. Questo è un sistema che si può trovare in uno di un insieme finito di stati, ognuno dei quali è fisicamente distinto.
Spesso è conveniente rappresentare lo stato di questa memoria come una stringa di simboli o, più semplicemente, come stringa di 0 e 1. In questo scenario, l’unità fondamentale della memoria è chiamata bit e possiamo misurare la “grandezza”
della memoria in termini del numero di bit necessari a rappresentare appieno lo stato della memoria.
Se la memoria soddisfa le leggi della fisica quantistica, lo stato della memoria potrebbe essere trovato in una sovrapposizione quantistica di diversi possibili stati “classici”. Se gli stati classici sono rappresentati da una stringa di bit,
la memoria quantistica può essere trovata in una qualsiasi sovrapposizione delle possibili stringhe di bit. Nello scenario quantistico, l’unità fondamentale della memoria è chiamata qubit.
La proprietà che definisce un computer quantistico è l’abilità di trasformare gli stati di memoria classici in stati di memoria quantistici, e viceversa. Questo è in contrasto con i computer classici nel senso che sono progettati per effettuare
computazioni con memoria che non devia mai da valori precisamente definiti. Per rendere chiaro questo punto, si consideri che l’informazione è solitamente trasmessa attraverso il computer come un segnale elettrico che può variare
tra due valori definiti di tensione. Se si inserisse un segnale a una tensione diversa da quelle due, il comportamento dei computer sarebbe indefinito.
Naturalmente, alla fine siamo “esseri classici” e possiamo osservare solo stati classici. Ciò significa che il computer quantistico deve completare il proprio calcolo dando un output classico.
Per produrre questi output classici, il computer quantistico è forzato a misurare parti della memoria a vari momenti durante la computazione.
Il processo di misura è intrinsecamente probabilistico, il che significa che l’output di un algoritmo quantistico è spesso casuale. Il compito di un progettatore di algoritmi quantistici è assicurarsi che la casualità sia adatta
ai requisiti del problema in questione. Per esempio, se il computer quantistico sta cercando un database quantistico per uno tra molti oggetti segnati, possiamo chiedere al computer di dare in output uno qualunque
degli oggetti segnati. Il computer quantistico riesce nel compito a patto che sia improbabile dare in output un oggetto non segnato.

Il modello prevalente di computazione quantistica descrive la computazione in termini di una rete di porte logiche quantistiche. Ciò che segue è un breve trattamento della materia che si basa sul capitolo 4 del libro di Nielsen e Chuang.
Si può rappresentare lo stato di una memoria informatica come un vettore di lunghezza uguale al numero degli stati di memoria possibili. Quindi una memoria costituita da
n {\textstyle n}
bit di informazione ha
2 n {\textstyle 2^{n}}
stati possibili, e il vettore che rappresenta quello stato di memoria ha
2 n {\textstyle 2^{n}}
entrate. Dal punto di vista classico, solo una delle entrate vale uno e tutte le altre zero. Il vettore dovrebbe essere visto come un vettore di probabilità e rappresenta il fatto che la memoria si trova in uno stato particolare con il 100% di probabilità
(cioè una probabilità di uno).
In meccanica quantistica, i vettori di probabilità vengono generalizzati a operatori densità. Questo è il fondamento matematico rigoroso per le porte quantistiche, ma il formalismo dei vettori degli stati quantistici di solito viene introdotta prima
perché è concettualmente semplice. Qui ci concentriamo solo sul formalismo dei vettori degli stati quantistici per semplicità

Frattale di Mandelbrot


Un frattale è un oggetto geometrico dotato di omotetia interna: si ripete nella sua forma allo stesso modo su scale diverse, e dunque ingrandendo una qualunque sua parte
si ottiene una figura simile all’originale.
Si dice quindi geometria frattale, la geometria (non euclidea) che studia queste strutture, ricorrenti ad esempio nella progettazione ingegneristica di reti, nel moto
browniano e nelle galassie.

Questa caratteristica è spesso chiamata auto similarità oppure autosomiglianza. Il termine frattale venne coniato nel 1975 da Benoît Mandelbrot nel libro
Les Objets Fractals:
Forme, Hasard et Dimension per descrivere alcuni comportamenti matematici che sembravano avere un comportamento “caotico”, e deriva dal latino fractus (rotto, spezzato),
così come il termine frazione; infatti le immagini frattali sono considerate dalla matematica oggetti di dimensione anche non intera.
Ad esempio, la curva di Koch ha dimensione
log ⁡ 4 log ⁡ 3 ≈ 1,261 86
.
I frattali compaiono spesso nello studio dei sistemi dinamici, nella definizione di curve o insiemi e nella teoria del caos, e sono spesso descritti in modo ricorsivo
da algoritmi o equazioni molto semplici, scritte con l’ausilio dei numeri complessi. Ad esempio l’equazione che descrive l’insieme di Mandelbrot è la seguente:
a n + 1 = a n 2 + P 0

dove
a n
e
P 0
sono numeri complessi.
Nel linguaggio comune, da qualche decina d’anni, è entrata a far parte la parola “programma”; il significato di questo termine è ormai noto: essendo il computer una macchina non intelligente ma un mero esecutore di ordini impartiti dall’esterno,
necessita di una serie di istruzioni, scritte in un opportuno codice detto linguaggio di programmazione, che costituiscono appunto il programma. Prima però di poter scrivere un programma il programmatore deve analizzare il problema
da risolvere, definire gli obiettivi da raggiungere, individuare i dati iniziali e finali e descrivere tutti i passi necessari per ottenere il risultato voluto. Affinché il computer sia in grado di eseguire con successo i compiti ad esso assegnati,
è necessario che la descrizione del procedimento sia accurata; è opportuno che alla macchina non venga indicato come risolvere un singolo problema, ma tutta una classe di problemi che differiscono per i dati iniziali.
Tutto ciò sottende al concetto di algoritmo che è generalmente usato come sinonimo di:

• procedura effettiva • procedimento di calcolo • metodo di risoluzione di un problema • insieme di regole per eseguire una data operazione

in effetti, la definizione corretta e completa è la seguente:
Un algoritmo1 è una procedura generale, finita, completa, non ambigua ed eseguibile che lavora su dati d’ingresso fornendo alcuni dati d’uscita. Analizziamo nel dettaglio le proprietà:

• generale: il metodo deve risolvere una classe di problemi e non un singolo problema (ad esempio deve essere in grado di calcolare l’area di tutti i triangoli e non solo quella di un particolare triangolo)
• finita: le istruzioni che la compongono ed il numero di volte che ogni azione deve essere eseguita devono essere finiti • completa: deve contemplare tutti i casi possibili del problema da risolvere
• non ambigua: ogni istruzione deve essere definita in modo preciso ed univoco, senza alcuna ambiguità sul significato dell’operazione • eseguibile: deve esistere un agente di calcolo in grado di eseguire ogni istruzione in un tempo finito

Gli algoritmi possono venire classificati secondo la seguente suddivisione:

• algoritmi deterministici • algoritmi non deterministici

Come già detto, affinché un dato problema possa venire risolto da un elaboratore è necessario che “qualcuno” definisca il relativo algoritmo risolutivo; tale algoritmo è scritto in linguaggio naturale, anche se codificato
con i formalismi precedentemente trattati. Gli algoritmi, per poter essere compresi e quindi eseguiti dai circuiti del computer, devono essere scritti in linguaggio macchina; tale linguaggio è molto lontano dal modo di pensare dell’uomo
e molto vicino alla struttura fisica del computer. I dati su cui operare e le istruzioni da eseguire sono stringhe binarie (sequenze di 0 e 1), si capisce quindi come la “traduzione” degli algoritmi in tale linguaggio sia prerogativa di pochi esperti
che conoscono a fondo i circuiti della macchina che deve eseguire le operazioni. Proprio per poter dare alla macchina, con una serie di comandi “vicini” al linguaggio naturale, degli ordini ad essa “comprensibili” sono
nati i linguaggi di programmazione. Questi sono linguaggi formali costituiti da parole in genere non difficili da ricordare (grammatica del linguaggio), combinate secondo rigide regole grammaticali (sintassi del linguaggio).
Per utilizzare un linguaggio di programmazione bisogna conoscere quindi la sua sintassi, la sua grammatica e la sua semantica. Da quanto detto risulta chiaro che un programma è la traduzione di un algoritmo in un linguaggio di programmazione.

Agli inizi degli anni ’50 per ovviare alle difficoltà che i programmatori incontravano, dovendo scrivere i loro programmi tenendo conto esattamente dell’hardware della macchina che avrebbe poi dovuto eseguirli, sono nati i linguaggi simbolici;
tali linguaggi consentono l’uso di un gruppo di parole, dette parole chiave, che possono essere combinate secondo una determinata sintassi. Negli ultimi cinquant’anni sono stati fatti passi da gigante, oggi esistono
infatti dei linguaggi molto vicini alla sintassi del linguaggio naturale, ma è chiaro che quanto più questi linguaggi risultano comprensibili, tanto più essi si allontanano dal linguaggio macchina.
Per interfacciare i linguaggi simbolici con il linguaggio macchina si sono sviluppati nel tempo dei programmi che traducono il programma simbolico (programma sorgente) in linguaggio macchina (programma oggetto);
i programmi traduttori si suddividono in:

• compilatori • interpreti

nella parte relativa agli ambienti di sviluppo saranno specificate le loro funzionalità.

I linguaggi di programmazione risultano così catalogati:

1) linguaggio macchina 2) linguaggi assemblativi 3) linguaggi procedurali 4) linguaggi non procedurali

Si è già parlato del linguaggio macchina, al primo livello e quindi molto vicino al computer ma scomodo per il programmatore; i linguaggi assemblativi, situati al secondo livello, presentano una struttura elementare ma possiedono
delle sigle che identificano le istruzioni. In questo tipo di linguaggi, per i quali in media una istruzione corrisponde ad una istruzione in linguaggio macchina, si usano riferimenti a componenti fisiche dell’elaboratore; è chiaro
che pur essendo in presenza di un linguaggio simbolico si è molto vicini al linguaggio macchina, in questo caso si parla di linguaggi a basso livello. Un esempio di linguaggio a basso livello è l’Assembler che usa istruzioni del tipo:
MOV A, 7 (scrive 7 nella variabile A), ADD A, 4 (somma 4 alla variabile A); INC A (incrementa A di 1) ecc. I linguaggi a livello 3 , che assieme a quelli di cui al punto 4 sono detti anche linguaggi evoluti (o ad alto livello) e sono frutto
di un progressivo tentativo di avvicinamento del linguaggio macchina al linguaggio naturale, sono nati verso la fine degli anni ’50, molto prima di quelli non procedurali, e sfruttano le tecniche della programmazione strutturata.
Precisiamo che la programmazione si dice strutturata quando nella costruzione degli algoritmi, e quindi dei programmi, si fa uso sistematico della sequenza della selezione e dell’ iterazione.

I principali programmi procedurali evoluti, tutt’oggi in uso, sono:

• FORTRAN (FORmula TRANslator): nato nel 1956 ed usato per scopi scientifici ed ingegneristici è stato il primo linguaggio ad alto livello

• COBOL (COmmon Businnes Oriented Language): nato nel 1960, ancora in uso per la sua semplicità ed elasticità, per applicazioni commerciali e gestionali

• Pascal: nato nel 1971 in ambiente prettamente scientifico, molto usato anche dal punto di vista didattico per l’uso coerente dei principi della programmazione strutturata

• C: nato nel 1974 sempre in ambito scientifico, con struttura aderente ai principi della programmazione strutturata, è molto diffuso e permette di scrivere programmi per risolvere problemi molto complessi.

Altri linguaggi molto usati furono il LISP, il Prolog, e l’ADA, anche questi sviluppati dal ’60 alla fine degli anni ’70. I linguaggi evoluti non procedurali, nati a partire dagli anni ’70, non sono linguaggi strutturati nel senso che il flusso
dei dati non può essere rappresentato per mezzo delle codifiche sopraesposte; la programmazione ad oggetti ed ad eventi sono sviluppi dei linguaggi non procedurali, in questo tipo di programmi il flusso delle informazioni non è “rigido”,
“prevedibile” ma varia a seconda degli eventi generati dall’azione dell’utente sugli oggetti. Esistono versioni ad oggetti del C e del Pascal (rispettivamente C++ e Turbo Pascal versione 5.5); esempio di linguaggio ad eventi è il Visual Basic
di linguaggio ad oggetti Power Builder.

La costruzione dei prodotti informatici non può essere un procedimento casuale, ma deve prevedere delle fasi che costituiscono quello che viene definito il ciclo di vita del software; tale ciclo si sviluppa dalla nascita del software per arrivare,
come passo estremo, alla sua definitiva eliminazione dal sistema. Si individuano sette fasi fondamentali:

• lo studio di fattibilità
• l’analisi e la specifica dei requisiti
• il progetto dell’architettura del sistema
• la realizzazione dei singoli moduli
• l’integrazione del sistema
• l’installazione del prodotto
• la manutenzione del prodotto

E’ chiaro che a monte della realizzazione di un progetto, anche non specificatamente di un programma, devono essere stimati i costi di sviluppo derivanti dalla realizzazione del prodotto stesso; tali costi devono essere poi confrontati
con i benefici ricavabili. Si sottolinea come la fase dello studio di fattibilità di un prodotto informatico, presenti delle particolarità che la rendono particolarmente delicata: errori dei programmatori che potrebbero rallentarne lo sviluppo,
reale stima della complessità del problema, non chiarezza del committente e conseguente difficoltà di capirne le reali esigenze, sono alcuni tra i molteplici fattori di difficile gestione. Nella fase di analisi e specifica dei requisiti vengono
esplicitate le proprietà richieste al programma e specificati gli obiettivi che si vogliono conseguire, fino a portare alla formalizzazione delle funzioni e delle strutture dati che risolvono il problema. Il progetto dell’architettura del sistema prevede
la scomposizione del problema generale in problemi più semplici detti moduli (metodo top-down); vengono quindi realizzati separatamente, per mezzo di un qualsiasi linguaggio, i vari moduli ed ogni singola componente è controllata e
verificata per garantire le specifiche richieste. Nella fase di integrazione del sistema le singole sottoparti vengono assemblate ottenendo il programma completo; la fase di verifica dell’intero sistema si esplicita tramite alcuni controlli effettuati
dal produttore (alfa-test) e, in seguito, da terze parti (beta-test). L’installazione è la fase in cui il sistema viene messo in opera; il programma viene trasportato dall’ambiente di sviluppo del produttore all’ambiente di utilizzazione del cliente;
dalla consegna del programma fino alla sua definitiva disinstallazione, vi è una lunga fase che prende il nome di manutenzione. Offrire la manutenzione del prodotto significa fornire al cliente assistenza sia per la correzione degli eventuali errori,
sia per modifiche che tengano conto delle mutate esigenze dello stesso.

Tutti i linguaggi di programmazione sono caratterizzati da un ambiente di sviluppo; l’ambiente di sviluppo è costituito da un insieme di programmi che agevolano la scrittura di programmi applicativi e la verifica della loro correttezza
(sintattica e strutturale, non logica). Ogni ambiente di sviluppo conterrà sicuramente un editor per scrivere il programma sorgente, ossia un programma scritto con istruzioni comprensibile all’utente ma non alla macchina; un compilatore
si occuperà poi della traduzione del programma sorgente in programma oggetto, formato da istruzioni direttamente eseguibili dall’elaboratore.

Per i linguaggi più elementari, il compilatore può essere sostituito da un interprete che, traduce il programma oggetto direttamente in istruzioni eseguibili dal processore ogni volta che il programma viene eseguito.
Il programma oggetto non può ancora essere eseguito, necessita di essere ancora trattato, “linkato”, per ottenere il programma eseguibile. Il programma che consente di assemblare i vari moduli si chiama, appunto, linker.
Il Debugger serve ad analizzare il programma nel corso della sua esecuzione verificando passo-passo il risultato delle singole istruzioni.