\chapter{Algoritmus řízení} \section{Návrh algoritmu} Úkolem v~této práci bylo navrhnout algoritmus vyjednávání mezi jednotlivými křižovatkami tak, aby koordinovanou změnou svých offsetů vytvořili zelenou vlnu a~tedy aby projelo co nejvíce vozidel bez zbytečného zastavování. Důležitým prvkem návrhu je funkce která, ohodnotí nastavení offsetu a umožní tak tedy porovnat různé jeho hodnoty a vybrat tu možná nejlepší. Jako míra kvality byl odhad počtu aut, která projedou křižovatkou bez zastavení. Pro výpočet hodnocení (v programu nazývaném \texttt{rating}) je třeba pro každý jízdní pruh na vjezdu do křižovatky znát délku fronty a očekávané časy příjezdů vozidel od sousední křižovatky. Délka fronty se v současné době získává ze simulátoru, příjezdy pak přímo od souseda. Ten je počítá podle vztahu \begin{equation} t_z = t_{zz} + \frac{d}{v_P} + \mathit{offset} \label{eq:tz} \end{equation} a \begin{equation} t_k = t_z + t_{dz} \;, \label{eq:tk} \end{equation} kde $t_z$ je čas začátku příjezdu aut a $t_k$ čas konce. Dále pak $t_{zz}$ je čas začátku svícení zelené, $d$ vzdálenost ke křižovatce, která žádá o časy příjezdů, $v_P$ průměrná rychlost vozidel, $\mathit{offset}$ nastavený offset a $t_{dz}$ délka svícení zelné. K těmto dvěma hodnotám se ještě přidává předpokládaný počet aut. Jeho odhad je získán z hustoty provozu v minulém cyklu. Křižovatka zasílající odhady takovýchto trojic údajů předává několik -- podle toho, kolik fází rozsvěcí zelenou ve směru připouštějícím jízdu k sousedovi, který o odhady žádal. Po shromáždění všech předpokladů od sousedů může agent přistoupit k samotnému výpočtu. Ten spočívá v tom, že agent spočítá příspěvky všech jízdních pruhů, u kterých má nějaké informace o časech příjezdů. Pro každý pruh se pak nejprve spočítá délka fronty v čase, kdy se na jeho odjezdu rozsvítí zelená. To znamená: \begin{equation} Q_V = Q + \sum_{i=1}^{k}{t_i n_i} \label{eq:qv} \end{equation} kde $Q_V$ je délka tak zvané virtuální fronty, zde jde o délku fronty při rozsvícení zelené, $Q$ je odhadovaná délka fronty, $t_i$ je délka $i$-tého intervalu, ve kterém přijíždějí vozidla, $n_i$ je počet těchto vozidel a $k$ je počet intervalů s příjezdy před rozsvícením zelené. Dále se pak spočítá délka virtuální fronty v okamžiku, kdy zelená přejde zpět na červenou. K tomu je třeba rozdělit interval od prvního očekávaného příjezdu nebo rozsvícení zelené (podle toho, co nastane dříve) po zhasnutí zelené (tento interval označíme $T$) na posloupnost intervalů $\left(T_i\right)^{k}_{i=1}$. $T_i$ jsou intervaly typu $\left$, kde $b_i=a_{i+1}$ $\forall i \in \left\{ 2, \ldots, n-1\right\}$, $a_1$ a $b_k$ jsou krajní body intervalu $T$ a $a_i$ jsou chronologicky řazené všechny významné body, tedy body, ve kterých se mění signalizovaný znak nebo ve kterých začíná či končí příjezd vozidel od souseda. S pomocí tohoto dělení pak můžeme počítat \begin{equation} Q_K = Q_V + \sum_{i=1}^{k}{u(T_i)}\,, \label{eq:qk} \end{equation} kde $u(T_i)$ je funkce $u:\; \left \rightarrow \mathbb{R}$ \begin{equation} u(T_i)= \begin{cases} n_i &\text{pokud v intervalu } T_i \text{ pouze přijíždějí vozidla} \\ -|T_i|\cdot c_o &\text{pokud v intervalu } T_i \text{ pouze odjíždějí vozidla} \\ n_i-|T_i|\cdot c_o &\text{pokud v intervalu } T_i \text{ přijíždějí i odjíždějí vozidla} \end{cases} \;. \label{eq:uti} \end{equation} $Q_K$ značí konečnou délku fronty, $Q_V$ je virtuální fronta z rovnice (\ref{eq:qv}), $k$ je počet intervalů „s různými vjezdy a výjezdy“. $|T_i|$ je délka intervalu $T_i$, $n_i$ počet vozidel, která přijedou v intervalu $T_i$ a $c_o$ je empiricky zjištěná konstanta -- počet vozidel, která za sekundu opustí křižovatku. Definice funkce $u(T_i)$ jak je zapsána vztahem (\ref{eq:uti}) není úplná. Ještě je nutné doplnit omezení \begin{equation} |u(T_i)| \begin{cases} \leq Q_i &\text{pokud v intervalu } T_i \text{ pouze odjíždějí vozidla} \\ \geq n_i &\text{pokud v intervalu } T_i \text{ přijíždějí i odjíždějí vozidla} \end{cases}\,, \label{eq:uti-omez} \end{equation} kde $Q_i$ je délka fronty na začátku intervalu $T_i$. Tyto podmínky zarčují, že pokud z jízdního pruhu jen odjíždějí vozidla, nemůže jich odjet více než jich čeká na začátku ve frontě a že pokud vozidla zároveň přijíždějí a odjíždějí, projede jich bez zastavení maximálně tolik, kolik dorazí od souseda. Pokud vyjde $Q_K$ záporné, představuje (odhadovaný) počet aut, která mohou křižovatkou projet bez zastavení a tato hodnota se tedy odečte od hodnocení dané křižovatky (a tím se tedy hodnocení zvýší). Vystává otázka jak zjistit aktuální délky fronta na jednotlivých ramenech jen pomocí údajů z detektorů, které jsou k dispozici. Ukazuje se, že toto není triviální problém a jeho řešení přesahuje rámec této práce. Z toho důvodu nejsou v programu délky front odhadovány tak, jak by to bylo v reálné situaci, ale používají se hodnoty, které lze získat ze simulátoru Aimsun. Podrobnější informace o modelování délky fronty představuje například \cite{pecherkova}. Při samotném vyjednávání pak mají agenti dvě role, jeden z nich je označen jako \emph{pasivní}, zatímco druhý je v pozici aktivního. Pasivní agent má pevně nastavený offset a jen reaguje na pokyny aktivního. Pokyny jsou buď žádost o očekávané časy příjezdů nebo návrh na změnu offsetu. Na první z nich agent vždy odpovídá zasláním požadovaných údajů, u druhého pak zvažuje, jestli by změna v celkovém součtu přinesla zlepšení \texttt{ratingu}. Z tohoto popisu už pak vyplývá role aktivního agenta. Ten stejně pracuje se žádostmi o časy příjezdů, navíc ale aktivně mění svůj offset a pokouší se vyjednat změnu u sousedů. Vyjednávací cyklus pak probíhá v několika krocích. Nejprve si všichni agenti vyžádají očekávané příjezdy vozidel na základě offsetů nastavených v minulém cyklu. S přihlédnutím k těmto očekáváním pak aktivní agenti spočítají \texttt{rating} svého nastavení offsetu a pokusí se zjistit, jestli by nějaká změna jejich offsetu nevedla k zlepšení hodnocení. Hledání nejlepšího offsetu probíhá ve třech krocích. Nejprve se porovná aktuální offset, offset zvýšený o 8 sekund a offset snížený o 8 sekund. Vybere se nejlepší ze tří možností a pokračuje se s ním stejným způsobem, jen uvažovaná změna je $\pm$ 4 sekundy. V posledním kroku je pak otestován posun o $\pm$ 2 sekundy. Když aktivní agenti naleznou své nejlepší offsety, rozešle se všem agentům zpráva o nalezení stabilního stavu, která obsahuje nové hodnoty očekávaných příjezdů vozidel. Nyní aktivní agenti vyzkouší, jestli by k dalšímu zlepšení nevedla změna offsetu u některého z jejich sousedů. Stejným způsobem jako při hledání vlastního nejlepšího offsetu zkusí odhadnout změnu \texttt{ratingu} při posunu sousedova offsetu o $\pm$ 4, 2 a 1 sekundu. Pokud má nejlepší hodnocení nenulová změna, zašle se sousedovi žádost o tuto změnu spolu se změnou \texttt{ratingu}, kterou by přinesla. Každý pasivní agent poté sesbírá všechny návrhy a otestuje, který z nich má největší součet změny ratingu u něj samotného a změny u navrhovatele a ten potom přijme za vlastní. Pokud by všechny návrhy přinesly zápornou změnu, jsou zamítnuty a žádná změna nenastává. V každém případě jsou pak rozeslány informace o novém stabilním stavu a s nimi opět očekávané příjezdy. Tímto každá křižovatka nalezne svůj konečný offset. Problém nastává při zaslání tohoto offsetu do řadiče křižovatky. Od toho není garantována okamžitá akce, způsob jakým žádaného offsetu dosáhne je jen v jeho režii a než se tak stane může trvat i několik cyklů. Z toho důvodu se vypočtený offset neposílá hned po nalezení, ale agent vždy v pěti cyklech napočítá optimální offset, z těchto pěti hodnot spočítá průměr a až ten se následně předá řadiči pro zpracování. Tím je také snížena reaktivnost agenta a zamezí se případným přehnaným reakcím na chvilkové změny poptávky, kterým stejně není možné se přizpůsobit. \section{Použité knihovny} Pro usnadnění práce a také z důvodu lepšího začlenění do současného řešení se v~programu se používají tři volně dostupné knihovny: \texttt{IT++}, zjednodušující práci s~vektory, maticemi a~poli, \texttt{BDM} (Bayesian Decision Making), která obsahuje užitečné nástroje pro práci s~popisy k~vektorům a~\texttt{libconfig}, sloužící především pro práci s~konfiguračními soubory. První dvě knihovny jsou distribuovány pod GPL licencí, třetí pak pod licencí LGPL. \subsection{IT++} \texttt{IT++} je knihovna pro C++, která obsahuje třídy a~funkce pro provádění některých matematických operací, zpracování signálů a~další. Pro účely této práce jsou zajímavé právě funkce matematické. V~knihovně jsou mj. zavedeny typy \texttt{vec} a~\texttt{ivec}. První jmenovaný je vektor obsahující prvky typu \texttt{double}, tedy čísla s~desetinou čárkou, druhý je pak složen z~prvků \texttt{int}, tedy čísel celých. Práce s~vektory je velmi intuitivní, navíc lze často používat syntaxi podobnou jako v~programu MATLAB. Vektor můžeme nadefinovat jedním z~těchto způsobů: \lstset{language=C++, showstringspaces=false} \begin{lstlisting} vec my_vector; vec my_vector(10); \end{lstlisting} přičemž první ze způsobů pro vektor nealokuje paměť. To je pak nutné udělat funkcí \lstinline{setsize()}. Následně je pak možné uložit do vektoru jednotlivé prvky a~to například jedním z následujících příkazů. \begin{lstlisting} vec a = "0 0.7 5 9.3"; // tedy a = [0 0.7 5 9.3] ivec b = "0:5"; // tedy b = [0 1 2 3 4 5] vec c = "3:2.5:13"; // tedy c = [3 5.5 8 10.5 13] ivec d = "1:3:5,0:2:4"; // tedy d = [1 3 5 0 2 4] vec e("1.2,3.4,5.6"); // tedy e = [1.2 3.4 5.6] \end{lstlisting} Navíc lze i-tému prvku vektoru \texttt{a} přistupovat pomocí \lstinline!a(i)! nebo \lstinline[]!a[i]!. Díky přetížení operátorů lze s~vektory přímo provádět běžné matematické operace, jako jsou: \begin{lstlisting} a+b // součet vektorů a+5 // přičtení čísla 5 ke všem prvkům vektoru a*b // skalární součin vektorů a*9 // vynásobení všech prvků vektoru číslem 9 \end{lstlisting} a~tak podobně. Práce s maticemi pak funguje podle obdobných pravidel. (Array) \subsection{BDM} Knihovna \texttt{BDM} (Bayesian Decision Making) se zabývá, jak název napovídá, bayesovským rozhodováním. Ovšem v~této práci jsou z~ní použity jen třídy \texttt{UI}, \texttt{RV}, \texttt{datalink} a~\texttt{logger}. Třída \texttt{UI} (User Info) slouží pro ukládání a~čtení libovolných uživatelských dat. Zde se používá pro načtení konfigurace pro simulátor a~pro jednotlivé agenty a~dále jako formát pro ukládání a~posílání zpráv mezi agenty. Účelem třídy \texttt{RV} je poskytovat popisné informace k~datovému vektoru. Proměnná typu \texttt{RV} se skládá z~jedné nebo několika položek. Každá taková položka má svoje jméno (\texttt{name}) a~délku -- počet prvků, které tomuto jménu odpovídají. Součet délek všech takovýchto položek se potom musí rovnat délce vektoru, který je danou proměnnou typu \texttt{RV} popisován. Fungování je ilustrováno na obrázku \ref{fig:rv}. \begin{figure}% \centering \includegraphics[width=7cm]{rv}% \caption{Vektor typu \texttt{RV} (vlevo) fungující jako popis k vektoru typu \texttt{vec}~(vpravo). Každá položka z \texttt{rv\_vector} má v levém sloupci své jméno (\texttt{name}) a v pravém velikost (\texttt{size}). Například podvektor \texttt{c} má tedy tvar [5 4]}% \label{fig:rv}% \end{figure} Pro kopírování dat mezi vektory existuje třída \texttt{datalink}. Vytváří spojení mezi dvěma datovými vektory na základně shodně pojmenovaných prvků v k nim příslušných popisných vektorech. Po propojení vektoru s podvektorem pak funkce \texttt{datalinku} umožňují snadné kopírování dat oběma směry. Na závěr \texttt{Logger} je třída určená pro ukládání dat z~programu. Tvoří abstraktní vrstvu mezi programem a~samotným zápisem dat. Při použití se jen na začátku nastaví v~jaké formě chceme získat výsledné údaje (např. uložit do paměti, do souboru, do databáze, atd.) a~dále třídu používáme bez ohledu na tuto volbu. Je tím zajištěna flexibilita pro případ, že se změní požadavky na výstupy z~programu. \subsection{Libconfig} Konfigurační parametry pro simulaci se ukládají do souboru ve formátu, který zavádí knihovna \texttt{libconfig}. Jde o~textový formát, který je stručnější a~pro člověka lépe čitelný, než XML. (...rozvést nebo zrušit...) \section{VGS API} Velmi důležitým úkolem při simulaci reálné oblasti v mikrosimulátoru Aimsun je vložení reálných vstupních intenzit dopravy do zkoumaného modelu. Běžným postupem je ruční sčítání vozidel v dané oblasti, zpravidla v hodinovém rastru. Takto získaná data je možné vložit do simulátoru Aimsun jako hodinové zátěže, ovšem toto je třeba provést také ručně. %Jedním z nejdůležitějších úkolů při simulaci reálného zatížení dopravnéí sítě v mikrosimulátoru Aimsun je zatížení celé simulované dopravní sítě reálnými vstupními intenzitami dopravy, odpovídajícími měřením v simulované síti. Při běžných postupech se intenzity dopravy určují pomocí ručního sčítání dopravy, zpravidla v hodinovém rastru. Data, získaná tímto postupem, je možno potom přímo vložit opět ručně do Aimsunu jako hodinové dopravní zátěže. Přesnější možností je získat intenzity provozu z dat získaných přímo z dopravních detektorů instalovaných v předmětné oblasti. To však znamená ruční zadání stovek údajů do simulátoru. Právě z tohoto důvodu vzniklo VGS API. To se stará o nastartování Aimsunu a vpouštění vozidel do oblasti. Vjezdy vozidel se při tom řídí údaji v externím souboru, obsahujícím intenzity na jednotlivých ramenech. Uživatel pak jen zadá jméno a umístění tohoto souboru a vše ostatní se děje automaticky. Navíc lze volit mezi různými rozdělení pravděpodobnosti vjezdů, přičemž nejčastěji používané je rovnoměrné či Poissonovo rozdělení. %Pokud se snažíme o přesnější simulaci, můžeme pro zatižení dopravní sítě použít data získaná přímo z dopravních detektorů instalovaných v simulované oblasti. V takovém případě by bylo potřeba ručně vkládat několik set hodnot vstupních intenzit pro každý simulovaný den. VGS API vzniklo ve snaze tento handicap odstranit -- na začátku simulace rozhraní pouze uživatewl předá informace o tom, kde je umístěn soubor s intenzitami vjezdů do jednotlivých ramen a o jaká vjezdová ramena jde, VGS API se automaticky postará o nastartování Aimsunu a automaticky vpouští vozidla do systému podle údajů, obsažených v souboru s intenzitami vjezdů. Uživatel může volit různé druhy rozdělení pravděpodobnosti vjezdů, nejčastěji se používá rovnoměrné nebo Poissonovo rozdělení. Vedle práci se vstupní daty pro simulaci se VGS API stará i o zpracování dat výstupních. Aimsun sice zvládá export výsledků simulací do textových souborů a obsahuje i nástroje pro jejich vizualizaci, neumožňuje přímo ale některé důležité věci jako je například porovnání výsledků ze dvou různých scénářů. VGS proto v průběhu celého experimentu ukládá údaje o sledované oblasti a to jak pro jednotlivé sekce, tak pro celý systém. Ve výsledku uživatel získává údaje o počtu zastavení vozidla, o jeho zpoždění, průměrné rychlosti, době jízdy a době stání, o dopravím toku a hustotě dopravy na jednotlivých segmentech či o délkách front vozidel.Údaje o délkách front jsou specialitou VGS, Aimsun tento údaj pro jednotlivé jízdní pruhy přímo neposkytuje a VGS má proto implementován vlastní algoritmus pro jejich sčítání. %Druhým důležitám úkolem VGS API je zhromažďovat data potřebná pro vyhodnocení experimentu. Aimsun sice disponuje jednoduchým rozhraním pro vizualizaci dat a jejich export do textových souborů, není ale možné například porovnávat jednotlivé scénáře simulací. VGS API proto periodicky ukládá všechny klíčové ukazatele jak pro jednotlivé segmenty dopravní sítě, tak i pro celý simulovaný systém. Uživatel má po ukončení simulace k dispozici údaje o počtu zastavení vozidla, o jeho zpoždění, průměrné rychlosti, době jízdy a době stání, o dopravím toku a hustotě dopravy na jednotlivých segmentech či o délkách front vozidel. Vzhledem k tomu, že Aimsun neposkytuje informace o délce fronty vozidel pro každý na jízdní pruh zvlášť (k dispozici jsou pouze údaje o akumulované délce fronty vyjádřené početm vozuidel, která na segmentu dopravní sítě v daný okamžik stojí), má VGS API implementovaný vlastní algoritmus výpočtu délky fronty vozidel na každém jízdním pruhu a poskytuje uživateli i takto získané informace o délce fronty. Implementací VGS API je DLL knihovna napsaná v jazyce C pro 32 a 64 bitové systémy pro systémy Windows XP a výše. Navíc je součástí VGStoolbox, sada skriptů pro zpracování výstupních dat v programu Matlab. %Vlastní VGS API je implementováno v jazyce C jako DLL pro 32- a 64-bitové systémy počínaje Windows XP, rozhraní je doplněno funkcemi pro obsluhu API a vyhodnocování statistických informací v prostředí nástroje Matlab \section{Struktura programu} Fungování simulačního prostředí tak jak bylo navrženo v ÚTIA je na obrázku (\ref{fig:struktura}). Skladá se ze tří hlavních bloků, mikrosimulátoru Aimsun, emulátorů řadičů ELS3 a z tzv. Aimsun agenta. Aimsun agent představuje řídící prvek celého systému. Přes VGS API spouští Aimsun a stejným způsobem od něj průběžně přijímá hodnoty sledovaných dopravních veličin. Dále přes \texttt{eh_api} získává údaje z řadičů křižovatek. Na základě všech sesbíraných údajů pak sestavuje pokyny pro změny v signálních plánech a opět pomocí \texttt{eh_api} tyto pokyny zasílá řadičům. Na závěr komunikace mezi mikrosimulátorem a emulátory řadičů probíhá přes \texttt{ea_api}. \begin{figure}% \centering \includegraphics[width=\columnwidth]{aimsun-agents}% \caption{Struktura celé simulace}% \label{fig:struktura}% \end{figure} \section{Program \texttt{main_loop.exe}} \label{sec:main_loop} (!upravit podle obrazku!) Simulace je obsluhována z~programu \emph{main\_loop.exe}. Ten se stará o~načtení konfigurace, spuštění simulátoru Aimsun a~zajišťuje komunikačního prostředníka mezi jednotlivými agenty. Diagram jeho fungování je na obrázku \ref{fig:mainloop}. Program se spouští s~jedním parametrem, který představuje jméno konfiguračního souboru. Konfigurační soubor používá formát, který načítá knihovna \texttt{libconfig}. \begin{figure}[t]% \centering \includegraphics[width=8.5cm]{mainloop3}% \caption{Strukuta fungování programu \texttt{main_loop.exe}, podrobněji popsaná v podkapitole \ref{sec:main_loop}}% \label{fig:mainloop}% \end{figure} Program funguje tak, že na začátku načte konfigurační soubor. Pokud konfigurační soubor není zadán, program skončí s~chybou. Po načtení je spuštěn simulátor Aimsun s~parametry zapsanými ve skupině \texttt{system}. Jde především o~intenzitu provozu na vstupech do dopravní sítě a~délku simulace. Následně je dle konfigurace vytvořeno pole ukazatelů na agenty \texttt{Ags}. Při konstrukci jednotlivých agentů je volána jejich funkce \texttt{from_setting()} načítající konkrétní parametry každého agenta. Poté se vytváří instance třídy \texttt{logger} a její provázání s agenty. V tu chvíli se pouští i funkce \texttt{ds_register()} umožňující aktualizaci datalinků. (...) Pak již následuje ústřední for cyklus. Na jeho začátku se zapíší údaje do logu. Poté jsou přečtena aktuální data ze simulátoru a předána agentům ke zpracování funkcí \texttt{adapt()}. Pokračuje se vyjednávacím cyklem. Vyjednávací cyklus se zabývá obsluhou fronty zpráv. Nejprve frontu prohledá frontu zpráv a příslušným agentů předá jim určené zprávy zavoláním funkce \texttt{recieve()}. V případě, že nemožnosti doručit oznámí varování, ale pokračuje dál v práci. Nakonec všichni agenti mají možnost nějaké zprávy do fronty přidat -- k tomuto účelu existuje funkce \texttt{broadcast()}. Tato smyčka je ukončena ve chvíli, kdy žádný z agentů nevyšle žádnou zprávu nebo pokud se zopakuje tolikrát, kolik je nastavený limit v proměnné \texttt{max_cycles}. Po ukončení vyjednávání je u každého agenta zavolána funkce \texttt{act()}. To je okamžik, kdy mohou agenti ovlivnit řízení signalizace. Práce zde probíhá zkopírování vypočítaných hodnot do vektoru \texttt{glob_ut}, tedy do proměnné představující vektor vstupních hodnot $u_t$. Na závěr hlavního cyklu je celá simulace posunuta o jeden krok dále voláním funkce \texttt{step()} u loggeru \texttt{L}, \texttt{Ds} a u všech agentů. Tato smyčka se opakuje dokud není dosažen v konfiguračním souboru nastavený čas simulace. Na závěr je zapsán log soubor a celá simulace je ukončena. \section{Implementace navrženého algoritmu} %\lstset{language=[Visual]C++,showstringspaces=false,numbers=left, numberstyle=\tiny, numbersep=5pt, tabsize=2} %\begin{lstlisting} %for ( int tK=0; tK < Ds->max_length(); tK++ ) { % Ds->log_write ( ); // write stuff to % Ds->getdata(glob_dt); % for ( int i=0; i adapt(glob_dt); % } % % // NEGOTIATION CYCLE % // ends when Queue is empty or after defined number of cycles % int cycle=0; % do { % //DBG % MsgStore.writeFile("xxx"); % // parse message queue % for ( int m=Queue.getLength()-1; m>=0; m-- ) { % // go backwards - last mesages are discarded % for ( int i=0; i_name()) { % Ags(i)->receive(msg); % Queue.remove(m); % break; % // message delivered; % } % } % } % if (Queue.getLength()>0){ % bdm_error("undelivered messages - % probably unknown neighbours"); % } % % for ( int i=0; i broadcast(Queue); % } % % cycle++; % } % while ((Queue.getLength()>0) && (cycle