• Non ci sono risultati.

Codice Quello che segue è il codice java che implementa l’algoritmo ALL- BRIDGE e la rete virtuale creata per l’esecuzione dello stesso.

N/A
N/A
Protected

Academic year: 2021

Condividi "Codice Quello che segue è il codice java che implementa l’algoritmo ALL- BRIDGE e la rete virtuale creata per l’esecuzione dello stesso."

Copied!
20
0
0

Testo completo

(1)

Codice

Quello che segue è il codice java che implementa l’algoritmo

ALL-BRIDGE e la rete virtuale creata per l’esecuzione dello stesso.

Tipi di dato

Verranno prima presentati i tipi di dato che sono stati creati e poi le

procedure che ne fanno uso per la simulazione della rete e

l’esecuzione dell’algoritmo.

Il tipo nodo rappresenta un nodo della rete, al cui interno sono

definite tutte le strutture dati necessarie.

class nodo {

int nome; /* nome del nodo */

arco[] in; /* insieme degli archi incidenti */

int ind; /* indice (dimensione) del vettore degli archi */

/* incidenti */

Tree[] SPT; /* alberi SPT radicati nel nodo i-esimo */ Position[] posizioni; /* posizione del nodo nell'albero */

/* SPT i-esimo */

int[] distanza; /* distanza dalla radice dell'albero */

(2)

APPENDICE: Codice

Vector[] LL; /* rappresenta la lista del archi */ /* incidenti che non appartengono */

/* all'albero SPT per ogni */

/* SPT i-esimo */

alfa[] vect_alfa; /* vettore degli alfa (un elemento */

/* per ogni SPT) */

elem_routing[] TabRout; /* tabella di routing */ /* (un elemento per ogni */

/* destinazione) */

int ind1 = 0; /*indice della tabella di routing */ }

Il tipo arco rappresenta un link della rete ed è composto dal nome dei

nodi che collega e dal suo peso.

class arco {

int a; /* nome del primo nodo */

int b; /* nome del secondo nodo */

int peso; /* peso dell'arco */

}

Il tipo seguente rappresenta l’elemento di base della tabella di routing

contenuta in ogni nodo:

class elem_routing {

int dest; /* nome del nodo destinazione */

int next; /* nome del nodo successivo nel */

/* percorso verso la destinazione */

arco swap_edge; /* arco di swap */

(3)

APPENDICE: Codice

Un elemento di tipo SPT serve per memorizzare un albero di

copertura di costo minimo ed è rappresentato semplicemente come

una collezione di archi.

class SPT {

arco[] albero; /* SPT, memorizzato come */

/* collezione di archi */

}

I tipi mostrati di seguito implementano i canali di comunicazione

utilizzati dai nodi per comunicare fra di loro come se fossero link.

Quindi sono realizzati utilizzando una struttura dati per contenere i

messaggi e due metodi sincronizzati per permettere che il canale

risulti consistente.

class canaleR {

private Vector inputR = new Vector(10,2);

/* questo vettore funge da canale di */ /* comunicazione (per le richieste) quindi */ /* l'accesso deve essere fatto in modo */

/* Synchronized */

synchronized void send(elemento_canale e) { inputR.add(e);

notify(); }

synchronized elemento_canale receive() { if (inputR.isEmpty()) {

try {

wait();

/* se mi trovo qui vuol dire che */

/* qualcuno ha scritto nel canale */

}

catch (InterruptedException e) {} }

return (elemento_canale) inputR.remove(0); }

(4)

APPENDICE: Codice

}

class canaleC {

private Vector inputC = new Vector(10,2);

/* questo vettore funge da canale di */ /* comunicazione (per le scelte) quindi */ /* l'accesso deve essere fatto in modo */

/* Synchronized */

synchronized void send(elemento_canale e) { inputC.add(e);

notify(); }

synchronized elemento_canale receive() { if (inputC.isEmpty()) {

try {

wait();

/* se mi trovo qui vuol dire che */

/* qualcuno ha scritto nel canale */

}

catch (InterruptedException e) {} }

return (elemento_canale) inputC.remove(0); }

}

Per ultimo, ma non per importanza, è presentato il tipo di dato grafo

che rappresenta la rete: oltre a contenere una collezione di nodi ed

archi, contiene anche la lista di tutti gli alberi di copertura di costo

minimo ognuno radicato in un diverso nodo.

class grafo {

int n_nodi = 0;

nodo[] V; /* insieme dei nodi */

arco[] E; /* insieme degli archi */

SPT[] tree; /* insieme degli SPT del grafo */

(5)

APPENDICE: Codice

Procedure

Vengono ora mostrate le procedure create ed utilizzate per la

simulazione della rete e di seguito quelle per la simulazione

dell’esecuzione dell’algoritmo distribuito ALL-BRIDGE.

La seguente procedura è stata scritta per acquisire la

rappresentazione di una rete vista come grafo, da un file di testo. La

procedura restituisce un elemento di tipo grafo in cui ogni nodo

conosce quali sono i propri archi incidenti.

static grafo acquisisci_grafo(String file)

{ /* il file deve avere questo formato: */

/* numero nodi */

/* nodo1 nodo2 peso */

/* nodox nodoy peso */

/* ... */ int nodi=0; grafo gr; int source; int dest; int cost; try {

FileReader fin = new FileReader( file );

BufferedReader graph = new BufferedReader( fin );

int i = 0;

/* Lettura archi e inserimento */

String line;

line = graph.readLine( );

/* legge il numero di nodi */

StringTokenizer st = new StringTokenizer( line ); nodi = Integer.parseInt(st.nextToken( )); gr = new grafo(nodi);

(6)

APPENDICE: Codice

gr.n_nodi = nodi;

i++;

while( ( line = graph.readLine( ) ) != null ) {

st = new StringTokenizer( line ); try

{ if( st.countTokens( ) != 3 )

throw new Exception( );

source = Integer.parseInt(st.nextToken( )); dest = Integer.parseInt(st.nextToken( )); cost = Integer.parseInt(st.nextToken( )); gr.E[i-1].a = source; gr.E[i-1].b = dest; gr.E[i-1].peso = cost; } catch( Exception e ) { System.err.println( e + " " + line ); } i++; }

/* inserimento dei nodi */

for (int k = 0; k<nodi; k++) { gr.V[k].nome = k+1; }

/* elaborazione del grafo per fare in modo */

/* che ogni nodo sappia quali sono gli archi incidenti */

for (int ind_arco=0; ind_arco<gr.E.length; ind_arco++) { if (gr.E[ind_arco].a != 0) { gr.V[gr.E[ind_arco].a-1].in[gr.V[gr.E[ind_arco].a-1].ind] = gr.E[ind_arco]; gr.V[gr.E[ind_arco].a-1].ind++; gr.V[gr.E[ind_arco].b -1].in[gr.V[gr.E[ind_arco].b-1].ind] = gr.E[ind_arco]; gr.V[gr.E[ind_arco].b -1].ind++; } } return gr;

(7)

APPENDICE: Codice

catch( java.io.IOException e ) { Sytem.err.println( e ); System.exit(0); } return null; }

La procedura mostrata di seguito, è stata scritta per calcolare,

partendo da un grafo e da un nodo “radice” un albero di copertura dei

cammini di costo minimo, utilizzando l’algoritmo di Dijkstra

opportunamente rivisto per funzionare anche nel caso di reti con archi

bidirezionali.

Inoltre la procedura è in grado di elaborare per ogni nodo le etichette

di visita dell’albero relative ad una visita preorder traversal e

preorder inverted traversal.

Ovviamente il calcolo dell’albero SPT porta come conseguenza al

riempimento della tabelle di routing di tutti i nodi.

public static Tree calcola_SPT(nodo radice, grafo G) {

/* questa procedura calcola l’SPT del grafo G, radicato */ /* nel nodo “radice” utilizzando l’algoritmo di Dijkstra */

Tree albero = new NodeTree(); /* albero SPT */

Tree albero_inverso = new NodeTree(); /* albero SPT "inverso" */

albero.replaceElement(albero.root(), new Integer(radice.nome)); albero_inverso.replaceElement(albero_inverso.root(), new

Integer(radice.nome));

Position[] posizioni = new Position[G.n_nodi+1];

/* vettore che memorizza le posizioni dei nodi */

Position[] posizioni_inverse = new Position[G.n_nodi+1];

/* vettore che memorizza le posizioni dei nodi nell'albero inverso */

(8)

APPENDICE: Codice

posizioni_inverse[radice.nome] = albero_inverso.root(); int[] D = new int[G.n_nodi+1];

Vector P = new Vector(G.n_nodi);

/* rappresenta l'insieme "P" */

Vector N_P = new Vector(G.n_nodi);

/* rappresenta l'insieme "N-P" */

/* INIZIALIZZAZIONE */

P.add(radice);

/* inserire in N_P i nodi che non stanno in P */

for (int i=0; i<G.V.length; i++) { if ( G.V[i].nome != 0) {

if ( G.V[i] != radice ) { N_P.add(G.V[i]); } }

}

D[radice.nome]=0;

/* D[j] = d(1,j), oppure = “inifinito” se l’arco non esiste */ for (int i=1; i<G.n_nodi+1; i++){

if ( i != radice.nome ) D[i] = 10000; /* sta per “infinito” */ }

for (int k=0; k<radice.in.length; k++) {

if (radice.in[k].a == radice.nome) { D[radice.in[k].b] = radice.in[k].peso; } else { D[radice.in[k].a] = radice.in[k].peso; }

}

/* FINE INIZIALIZZAZIONE */

int s=0;

boolean fine = false;

(9)

APPENDICE: Codice

/* PASSO 1 */

/* trovare il minimo D[j] con j appartenente a N_P */

int indice = 0; int min = 10000;

arco arcoInserito = new arco(); for (int i=0; i<D.length; i++) {

if (( D[i] < min ) && ( D[i] != 0 )) {

/* se l'elemento è presente in N-P allora il minimo viene aggiornato */ if (N_P.contains(G.V[i-1])) { min = D[i]; indice = i; } } } /* aggiornamento di P */ P.add(G.V[indice-1]);

int nodoInserito = G.V[indice-1].nome;

/* serve per creare l’ SPT */

/* inserimento dell'arco nel SPT */

nodo nodo_estr = new nodo(G.n_nodi); int peso;

boolean esci;

for (int k=0; k<P.size(); k++) { esci = false;

nodo_estr = (nodo) P.get(k); peso = D[nodo_estr.nome];

for (int i=0; i<G.V[indice-1].ind; i++){

if (( G.V[indice-1].in[i].a == nodo_estr.nome ) | ( G.V[indice-1].in[i].b == nodo_estr.nome ))

{ if ( D[G.V[indice-1].nome] == D[nodo_estr.nome] + G.V[indice-1].in[i].peso ) { /* inserire l'arco in SPT e uscire dai cicli */

(10)

APPENDICE: Codice

G.tree[radice.nome].albero[s] = G.V[indice-1].in[i]; s++;

arcoInserito = G.V[indice-1].in[i];

/* serve per creare il SPT */ esci = true;

break; } } }

if (esci == true) break; }

/* fine dell'inserimento dell'arco nel SPT */

/* aggiornamento di N-P */

N_P.remove(G.V[indice-1]);

/* avendo a disposizione nodoInserito e arcoInserito, */ /* è possibile creare la struttura albero con NodeTree */ /* (“s” indica il numero di nodi inseriti); ad ogni nodo inserito */ /* è necessario salvare i "position" così quando si deve */ /* inserire un figlio di un nodo con una certa "position" si */

/* conosce il punto in cui inserirlo */

int figlio, padre;

if (arcoInserito.a == nodoInserito)

{ padre = arcoInserito.b;

figlio = arcoInserito.a; }

else { figlio = arcoInserito.b; padre = arcoInserito.a; }

Position node = albero.insertFirstChild(posizioni[padre], new Integer(figlio)); posizioni[figlio] = node;

Position node_inverso =albero_inverso.insertLastChild(

posizioni_inverse[padre], new Integer(figlio)); posizioni_inverse[figlio] = node_inverso;

(11)

APPENDICE: Codice

/* una volta inserito il nodo nell'albero SPT, è possibile */

/* aggiornare la tabella di routing per quel nodo */

G.V[indice-1].TabRout[G.V[indice-1].ind1].dest = radice.nome; G.V[indice-1].TabRout[G.V[indice-1].ind1].next = padre; G.V[indice-1].ind1++; /* FINE PASSO 1 */ if ( P.size() == G.n_nodi ) {

/* l'algoritmo si deve fermare */

fine = true; } if ( fine != true ){ /* PASSO 2 */ /* aggiornare i D[j] */

nodo estratto = new nodo(G.n_nodi); int nome_nodo = 0;

int minimo=10000; /* "infinito" */ int nuovo_peso = 0;

for (int j=0; j<N_P.size(); j++) { estratto = (nodo) N_P.get(j); nome_nodo = estratto.nome; minimo = 10000;

for (int k=0; k<estratto.ind; k++) {

if ( nome_nodo == estratto.in[k].a ) {

nuovo_peso = estratto.in[k].peso + D[estratto.in[k].b]; } else { nuovo_peso = estratto.in[k].peso + D[estratto.in[k].a]; }

if (nuovo_peso < minimo) { minimo = nuovo_peso; }

}

if ( D[nome_nodo] < minimo ) {/* D[j] rimane quello che è */ } else { D[nome_nodo] = minimo; }

} } }

(12)

APPENDICE: Codice

/* ora occorre calcolare gli alfa(a,b), attraverso la visita */ /* preorder e preorder inverted dell'albero e inserire */

/* questi dati nei nodi */

PositionIterator nodes = new TreeTraversal(albero, 1); int i=0; String estratto; while(nodes.hasNext()) { estratto = nodes.nextPosition().element().toString(); G.V[Integer.parseInt(estratto)-1].vect_alfa[radice.nome-1].a = i+1; i++; }

PositionIterator nodes_i = new TreeTraversal(albero_inverso, 1); i=0; while(nodes_i.hasNext()) { estratto = nodes_i.nextPosition().element().toString(); G.V[Integer.parseInt(estratto)-1].vect_alfa[radice.nome-1].b = i+1; i++; } /* alfa(a,b) memorizzati */

/* ora l’albero calcolato deve essere memorizzato in tutti i nodi */

for (int k=0; k<G.n_nodi; k++){

G.V[k].SPT[radice.nome] = albero; }

/* poi viene memorizzata la posizione dei nodi nell'albero */

/* all'interno dei nodi stessi */

for (int k=1; k<posizioni.length; k++){ if (posizioni[k] == null) break;

G.V[k-1].posizioni[radice.nome] = posizioni[k]; }

/* si memorizza la distanza dei nodi dell'albero dalla radice */

(13)

APPENDICE: Codice

if (posizioni[k] == null) break;

G.V[k-1].distanza[radice.nome] = D[k]; } SPT[] alb = G.tree; return albero; }

È stato necessario scrivere la seguente procedura, che per ogni nodo

e per ogni albero SPT calcola la lista degli archi incidenti che non

fanno parte dell’SPT, perché tale lista fa parte dei prerequisiti

necessari ai nodi per poter eseguire l’algoritmo ALL-BRIDGE.

public static void calcola_vicini_non_SPT(int nome_nodo) {

/* calcola i vicini non appartenenti agli SPT per il nodo */ /* i-esimo deve essere fatto una volta per ogni albero SPT */

for (int n_alb=0; n_alb<G.n_nodi; n_alb++) {

for (int i=0; i<G.V[nome_nodo].ind; i++) {

elemento da_inserire = new elemento();

if (!appartiene(nome_nodo, n_alb, G.V[nome_nodo].in[i])) {

/* se l'arco "e" non appartiene al SPT deve */ /* essere inserito L(x). prima di inserirlo, occorre */ /* calcolare il peso dell'arco "d(e)" */ da_inserire.link = G.V[nome_nodo].in[i];

da_inserire.peso = calcola_peso(nome_nodo, n_alb,

G.V[nome_nodo].in[i]); inserisci(nome_nodo, n_alb, da_inserire);

} } } }

(14)

APPENDICE: Codice

Quello che segue è invece il corpo del thread, che rappresenta un

generico nodo della rete, il quale dovrà poi eseguire l’algoritmo

distribuito.

Al suo interno oltre ad essere presenti le strutture dati necessarie, è

contenuto anche il codice dell’algoritmo; grazie all’esecuzione di n

istanze dell’algoritmo, eseguite ognuna da un thread(nodo) diverso, la

computazione distribuita verrà portata a termine.

public class thread_nodo extends Thread {

public final int Computing = 1; /* rappresentano */ public final int Swapped = 2 ; /* gli stati in cui */ public final int Waiting = 3 ; /* si può trovare un nodo */ public final int Done = 4;

int stato; nodo mio_nodo; elemento swap_edge;

Vector LX = new Vector(0,2); /* rappresenta la lista dei */

/* Possibili swap edge */

Vector Req = new Vector(0,2); /* rappresenta la lista delle */ /* richieste da fare ai figli */ canaleC CC = new canaleC(); /* canale di comunicazione */ /* per i messaggi in ingresso */ canaleR CR = new canaleR(); /* canale di comunicazione */ /* per i messaggi in uscita */ public void run() {

int cond = 0; while (cond == 0) {

switch (this.stato) { /* struttura dell’algoritmo */ case 1: computing(); break;

case 2: swapped(); break; case 3: waiting(); break;

case 4: { done(); cond=1; break; } }

(15)

APPENDICE: Codice

Di seguito sono presentate le quattro procedure che rappresentano i

quattro stati in cui un nodo, nel corso dell’esecuzione dell’algoritmo

distribuito ALL-BRIDGE, si può venire a trovare. Queste

rappresentano il cuore dell’algoritmo e al loro interno sono

diversificate a seconda che in nodo su cui sono eseguite sia la radice

dell’SPT oppure un nodo interno oppure una foglia.

public void computing () {

if (!albero.isRoot(pos)) /* se il nodo non è la radice */

{ /* ricavare il nome del padre */

padre = albero.parent(pos);

String Spadre = padre.element().toString(); n = Integer.parseInt(Spadre);

}

if (albero.isExternal(pos)) /* se il nodo è una foglia */

{ /* sceglie il suo bridge e */

/* lo invia al genitore */ mybridge = ChooseMin(mio_nodo.vect_alfa[this.n_alb-1]);

if (mybridge != null) sendCTo("Choice", mybridge,n); else sendCTo("null", mybridge,n);

became(Swapped);

/* si prepara ad attendere richieste dal genitore */ return;

}

else /* se è un nodo interno */

{ /* attende che ognuno dei proprio figli abbia inviato il proprio bridge */ int num_figli = albero.numChildren(pos);

int k = 0;

for (int i=0; i<num_figli; i++) {

// receive da un figlio sonBridge = receiveC();

// se l'arco è "feasible" lo memorizza if (!sonBridge.s.equals("null"))

{ if (feasible(sonBridge.ele.link, mio_nodo.vect_alfa[this.n_alb-1]))

(16)

APPENDICE: Codice

k++;

}

/* se non è "feasible", fai una richiesta al figlio */

else

{ elemento_canale elem = new

elemento_canale("Request",null,sonBridg e.mitt,mio_nodo.vect_alfa[this.n_alb-1]); this.Req.add(elem); this.check++; } } else k++; }

/* se lo swap edge di tutti i figli è "feasible" scegli */ /* il migliore, se il nodo era un nodo interno, */ /* altrimenti se era la radice, deve terminare l’algoritmo */

if (albero.isRoot(pos)) /* se il nodo era la radice */

{ /* si deve terminare l'algoritmo */

/* invio un messaggio a tutti i figli che a */ /*loro volta lo invieranno ai loro figli... */ PositionIterator Pfigli = albero.children(pos);

String Sfiglio; int figlio; while(Pfigli.hasNext()) { Sfiglio = Pfigli.nextPosition().element().toString(); figlio = Integer.parseInt(Sfiglio);

sendRTo("Done", null, figlio, null); }

became(Done); return; }

if (k == num_figli) /* se tutti i figli hanno inviato un */

/* bridge feasible, deve essere */

/* scelto il migliore e deve essere */

/* inviato al genitore */

(17)

APPENDICE: Codice

if (mybridge != null) sendCTo("Choice", mybridge,n); else sendCTo("null", mybridge,n);

became(Swapped);

/* si prepara ad attendere richieste dal genitore */ return;

} else {

became(Waiting);

/* si prepara ad attendere risposte dai figli */ return;

} } }

public void swapped () {

/* ricavare il nome del padre */

padre = albero.parent(pos);

String Spadre = padre.element().toString(); n = Integer.parseInt(Spadre); this.check = 0; while(true) { // ricevi richiesta rec = receiveR(); if (rec.s.equals("Request")) { if ( albero.isExternal(pos) ) { // calcola minimo mybridge = ChooseMin(rec.label);

/* invia la risposta al genitore */

if (mybridge != null) sendCTo("Choice", mybridge,rec.mitt); else sendCTo("null", mybridge,rec.mitt); }

else

{ int precedente = 0;

for (int i=0; i<LX.size(); i++) {

(18)

APPENDICE: Codice

if (estratto.ricevuto == 1)

{ if (estratto.mitt != precedente) {

precedente = estratto.mitt;

/* se l'arco inv. del figlio i-esimo non è feasible */ if ( !(feasible(estratto.ele.link,rec.label)) )

{ /* invia una richiesta al figlio */ elemento_canale elem = new

elemento_canale("Request", null, estratto.mitt, rec.label); this.Req.add(elem); this.check++; } } } } if (this.check > 0) { this.comp = 1; this.alfa_attuale = rec.label;

/* se è stata fatta richiesta ad almeno un figlio */ became(Waiting); /* bisogna mettersi in attesa */

/* di ricevere la risposta */ return; } else { /* calcola il minimo */ mybridge = ChooseMin(rec.label);

/* invia l'arco scelto al genitore */

if (mybridge != null) sendCTo("Choice", mybridge,n); else sendCTo("null", mybridge,n);

} } } else { if ( albero.isExternal(pos) ) { became(Done); return; } else

{ /* si deve propagare il messaggio che */

/* l'algoritmo è terminato */

(19)

APPENDICE: Codice

int figlio; while(Pfigli.hasNext()) { Sfiglio = Pfigli.nextPosition().element().toString(); figlio = Integer.parseInt(Sfiglio);

sendRTo("Done", null, figlio, null); } became(Done); return; } } } }

public void waiting () {

while (this.Req.size() > 0) /* prima di tutto si devono */ /* inviare tutte le richieste */

/* da fare ai figli */

{

ric = (elemento_canale)this.Req.remove(0); sendRTo(ric.s, null, ric.mitt, ric.label); }

/* poi si aspettano le risposte */ for (int i=0; i<check1; i++)

{

/* riceve l'arco (giusto) dal figlio */

rec = receiveC(); if (!rec.s.equals("null")) { /* memorizza l'arco */ insert(rec); } this.check--; } if (!albero.isRoot(pos)) { int n = 0;

/* ricavare il nome del padre */

padre = albero.parent(pos);

(20)

APPENDICE: Codice

n = Integer.parseInt(Spadre);

/* sceglie il minimo tra gli archi */

if (comp == 0)

mybridge = ChooseMin(mio_nodo.vect_alfa[this.n_alb-1]); else

mybridge = ChooseMin(this.alfa_attuale);

/* invia l'arco al genitore */

if (mybridge != null) sendCTo("Choice", mybridge,n); else sendCTo("null", mybridge,n); became(Swapped);

return; }

else {

/* si deve terminare l'algoritmo */

/* invio un messaggio a tutti i figli che a */

/*loro volta lo invieranno ai loro figli... */

PositionIterator Pfigli = albero.children(pos); String Sfiglio; int figlio; while(Pfigli.hasNext()) { Sfiglio = Pfigli.nextPosition().element().toString(); figlio = Integer.parseInt(Sfiglio);

sendRTo("Done", null, figlio, null);

}

became(Done); return;

} }

public void done () {

/* il nodo deve memorizzare il proprio swap-edge */

if (LX.isEmpty()) estratto = null; else{ estratto = ChooseMin(mio_nodo.vect_alfa[this.n_alb-1]); this.swap_edge = estratto; } return; }

Riferimenti

Documenti correlati

[r]

Nascono i sistemi a chiave asimmetrica, composti da una chiave pubblica, nota a tutti ed usata per la cifratura, e da una chiave privata,.. nota ad una sola persona ed usata per

public class verificaDupList implements AlgoDup { public boolean verificaDup(List S){…}}. public class verificaDupOrdList implements AlgoDup { public boolean

Progetto Lauree Scientifiche.

Irroratela quindi con un quarto di litro di vino e con altrettanto fumetto di pesce, poi passatela nel forno a 150 gradi per 45 minuti circa, unendo, a metà cottura, i

[r]

Vogliamo dimostrare che la matrice simmetrica A ha abbastanza autodimensione, cio` e vogliamo usare il Teorema fondamentale della diagonalizzazione.. Allora assumiamo il contrario,

 Se nessuna variabile artificiale è ancora in base, ho una base fatta da sole colonne del problema originario e posso partire con Fase 2 su esso.  Se ho ancora variabili