Esame di Intelligenza Artificiale, A.A. 2000/2001
Progetto di costruzione di robot autonomi
Relazione
Giuseppe Lipori, Fabrizio Magni, Jacopo Mantovani
Il robot accentratore
Dato il programma "delivery" originale (Levesque, Pagnucco), dove si aveva in sostanza un percorso aperto curvo, spezzato da stazioni intermedie, si è deciso di generalizzare il più possibile, sfruttando se possibile una certa simmetria, implementando un percorso a forma di "ragnatela", con due tipi di stazioni: il primo è di tipo "center", posto al centro della ragnatela, mentre il secondo è di tipo "suburbs", situato agli estremi. Sono possibili più stazioni "center", così come più stazioni "suburbs". Inizialmente il robot viene situato su uno dei centri della ragnatela (stazioni che vengono distinte dai "sobborghi" per un bassissimo valore di luminosità, ottenuto con un foro in un tavolo che presenta poca rifrazione alla luce). Alla partenza del programma, il suo compito è di scegliere casualmente un percorso verso la periferia, recarvisi e, una volta raggiunta una stazione, aprire le pinze e rimanere in attesa di un evento esterno, ossia la pressione su uno dei due sensori di contatto. Il comportamento seguente del robot dipende da cosa viene premuto, e si distingue nel seguente modo:
Quanto descritto sopra si ripete in una sequenza infinita, senza che l’utente debba inserire a terminale alcun goal, essendo quest’ultimo inscritto nel codice1.
Figura 1: un esempio di percorso per il robot accentratore
Data l’alta simmetria del percorso (una sorta di "ragnatela") e l’arbitrarietà della posizione delle stazioni su di esso (invece che numerare ciascuna stazione, come nel delivery originale, ci siamo limitati a definirne il tipo), la forma del tracciato è in realtà del tutto arbitraria rispetto alla nostra applicazione. E’ sempre possibile cioè progettare un percorso qualsiasi, oppure modificare il percorso preesistente, con l’unico vincolo di mantenere il tipo di stazioni adoperate, ovvero center (da cui il robot parte e dove viene rilasciato un eventuale carico) e suburbs (ove il robot può raccoglierre un oggetto). Tutto questo senza modificare il codice. Inoltre, non solo la topologia del percorso è modificabile, ma anche il numero di stazioni, di qualsivoglia tipo, in esso. In pratica vi possono essere due o più stazioni (comunque almeno una) di tipo center, così come un numero arbitrario di stazioni di tipo suburbs. Sotto un certo punto di vista, questo fatto può essere visto come una sorta di autonomia del robot rispetto al tipo di percorso: a fronte di una modifica del tracciato, virtualmente possibile anche nel corso dell’esecuzione di una sessione del programma, il robot reagisce adattandosi ad esso. In un programma come il delivery originale, invece, ogni stazione era numerata, ed il percorso era modificabile solo a fronte di una modifica del programma, venendosi a perdere ogni autonomia in questo senso. Inoltre si è deciso di eliminare il fluente primitivo direction in virtù di una ulteriore semplificazione del modello concettuale: il robot si muove sempre alla ricerca di un’altra stazione (center o suburbs), non tenendo conto della stazione di partenza, né facendo ipotesi su quella di arrivo. E’ dunque possibile attivare il robot in qualsiasi stazione della rete garantendo un comportamento coerente dello stesso.
Tutto questo si presta facilmente a suggestive interpretazioni, come per esempio quella di un agente intelligente che si aggira per una città (modellizzata dal tracciato) con il compito di raccogliere, ad ogni stazione di fermata, un eventuale oggetto da portare in un deposito (una stazione center), rilasciarlo, e riprendere il suo compito. Certamente la scelta casuale del percorso non consente di affermare con certezza assoluta che tutte le stazioni periferiche verranno visitate, ma è lecito pensare che vi è una buona probabilità che ciò accada, direttamente proporzionale con il tempo di esecuzione del programma.
Inoltre sono stati usati anche tutti e tre gli alloggiamenti disponibili per i sensori, nel modo seguente: il sensore luminoso viene utilizzato come nel delivery originario per seguire il tracciato; un sensore di contatto è destinato alla segnalazione (tramite azione esogena) della presenza di un oggetto da afferrare e quindi trasportare; infine, un altro sensore di contatto è adibito alla segnalazione (in una stazione) della mancanza di oggetti da caricare, di modo che il robot possa proseguire oltre verso un’altra stazione (in modo casuale).
Per la modifica del programma "delivery", essendo quest’ultimo composto da diversi file, in linguaggi diversi, si è scelta la seguente politica. Una volta scelto di distinguere tra due diversi tipi di stazioni, si è modificato dapprima il codice nqc, a più basso livello, ovvero si sono modificati i valori di soglia per il sensore di luminosità (SUBURBS_THRESHOLD, CENTER_THRESHOLD), al posto della costante ARRIVE_AT_STATION si sono introdotte due costanti ARRIVE_AT_CENTER e ARRIVE_AT_SUBURBS, ad identificare rispettivamente l’arrivo al centro della ragnatela e l’arrivo ad una stazione periferica (o meglio le rispettive due azioni esogene), e da ultimo si è introdotta la costante PUSH_GRASP_BUTTON, che è correlata all’azione esogena di pressione del sensore vicino alle pinze (c’è un oggetto da prendere e portare al centro), mentre la PUSH_GO_BUTTON, viene mantenuta (anche se evidentemente ne viene ridefinita la semantica). Il corpo del task goToNextStation viene di conseguenza modificato, così come il task monitorPushButton, che controlla quale dei due sensori di contatto viene premuto. L’aggiunta fisica del secondo sensore si lega al software con la definizione di GRASP_BUTTON come SENSOR_2.
A fronte di queste relativamente poche modifiche, il codice prolog/golog subisce cambiamenti sostanziali e numerosi. A basso livello, dalla main routine di controllo vengono rimossi tutti gli interrupt (e quindi pure le procedure) inerenti il conteggio delle stazioni, e ne vengono invece introdotti che verifichino la posizione del robot nella ragnatela (load_station e unload_station rispettivamente per la periferia ed il centro), con le relative procedure. A più alto livello, vengono deparametrizzati fluenti che prima avevano valori e argomenti riferiti al numero di stazione (ad esempio holding_delivery_for(N) diventa semplicemente holding_delivery, dal momento che la meta del robot per una delivery è sempre il centro, e anche delivery_requested, visto che oggetti da prendere sono sempre in periferia2), vengono introdotte nuove azioni esogene come arrive_at_center, arrive_at_suburbs e push_grasp_button. Assumono grande importanza i predicati causes_val, che determinano in sostanza il collegamento tra le azioni esogene e la variazione dei fluenti. Per una loro analisi si rimanda al codice, così pure come per altre modifiche, i.e. i predicati ti tipo initially, poss, actionNum.
Note a piè pagina:
1
Questo programma è una generalizzazione di una precedente versione, in cui il percorso era a forma di stella, e l’unica azione esterna possibile era di avvertire il robot della presenza di un oggetto, una volta arrivato in periferia, per farlo tornare al centro. Afferrato l’oggetto o no, il robot chiudeva le pinze, eseguiva una rotazione di 180 gradi, e riportava l’oggetto al centro, aprendo poi le pinze.2
Nel caso in cui venga premuto il push_grasp_button durante un tragitto, facendo sì che il robot afferri un oggetto in corsa, allora esso prosegue il suo cammino per poi rilasciarlo alla prima stazione raggiunta (sia center che suburbs); ivi esso aspetta una delle due azioni esogene previste, come a richiedere conferma di quanto raccolto per strada.Invece una pressione accidentale dul push_go_button durante la marcia non causa modifiche nel comportamento esibito dal robot, il che è coerente con il fatto che esso, mentre si muove, è già alla ricerca di una stazione.
Link ai file di codice (in grigio le righe di codice dei file originali, in nero le modifiche):