Zajęcia są we wtorki. Grupa nr 1 zaczyna zajęcia o 16:15, sala G-1-07. Grupa nr 2 o 18:00, sala G-1-10. Zapraszam również na konsultacje (pokój C-2-29 lub online, terminy w USOSwebie).
W trakcie semestru można mieć co najwyżej dwie nieusprawiedliwione nieobecności. Przekroczenie tego limitu oznacza brak zaliczenia.
Zaliczenia ćwiczeń wystawiane będą głównie w oparciu o projekty.
Wstępnie planowane są trzy, zobaczymy w jakim tempie będziecie je Państwo
realizować. Uaktualnienie: będą dwa projekty. Oprócz projektów będę
uwzględniał również aktywność i wiedzę wykazywaną na zajęciach.
W tym roku grupy są zbyt duże, aby w trakcie zajęć dało się ze wszystkimi studentami podyskutować o ich projektach. Podzielone są więc na półgrupy. W niektóre tygodnie będą zajęcia tylko dla pierwszej półgrupy, w inne tylko dla drugiej, a niekiedy będzie musiała przyjść cała grupa.
Harmonogram ćwiczeń:
Terminy oddawania projektów:
Nazwa „gramatyki kształtu” (ang. shape grammars) przywodzi na myśl gramatyki formalne, które są wykorzystywane do definiowania składni języków programowania. I rzeczywiście, gramatyki kształtu zaadaptowały na swoje potrzeby ideę reguły (nazywanej też produkcją), wywodu, itd. Zasadniczą różnicą jest to, że gramatyki formalne przekształcają ciągi znaków, a gramatyki kształtu — rysunki (w oryginalnym artykule nazywane kształtami, co moim zdaniem jest trochę mylące).
Tak jak są różne rodzaje gramatyk ciągowych, tak też można wymyślić i korzystać z różnych rodzajów gramatyk kształtu. W poniższym opisie będę starał się zaznaczać miejsca, w których można wybrać jedno z kilku alternatywnych podejść.
Regułę gramatyki kształtu definiuje się podając dwa rysunki, zwane lewą i prawą stroną reguły. Między nimi zwyczajowo umieszcza się strzałkę.
Regułę można zastosować (zaaplikować) do rysunku, jeśli jest w nim fragment pasujący do lewej strony reguły. Usuwa się wtedy dopasowany fragment i na jego miejsce wstawia kopię prawej strony reguły.
Niekiedy wymaga się, aby lewa strona zawierała się w prawej. Z punktu widzenia użytkownika zastosowanie takiej reguły niczego nie usuwa, dodaje tylko do rysunku nowe elementy.
Gramatykę definiuje się podając zbiór reguł oraz rysunek początkowy (startowy). Spotyka się też podejście, w którym rysunek początkowy zawsze jest pusty, a za to w zbiorze reguł jest co najmniej jedna z pustą lewą stroną i tylko takie reguły z pustą lewą stroną pasują do pustego rysunku.
Gramatyka generuje rysunki poprzez kolejne przekształcenia rysunku roboczego, który początkowo jest kopią rysunku startowego. Wybiera się regułę, dla której lewej strony istnieje dopasowanie, aplikuje ją i w ten sposób przekształca roboczy rysunek, a potem znów wybiera regułę itd. Ten proces nazywany jest „prowadzeniem wywodu w gramatyce”, a o otrzymywanych w kolejnych krokach rysunkach mówi się, że „zostały wywiedzione z rysunku początkowego”. Jeśli dla żadnej z reguł gramatyki nie można znaleźć dopasowania, to proces wywodu musi się zatrzymać.
Możliwe jest, że dla danego rysunku roboczego i danej reguły będzie istnieć kilka możliwych dopasowań (np. lewa strona to kwadrat, a na rysunku jest pięć takich kwadratów). Możliwe też, że dopasowania będzie miało kilka reguł. W takiej sytuacji można albo losowo wybrać regułę i jej dopasowanie, albo poprosić o decyzję użytkownika nadzorującego proces wywodu. Użytkownikowi można też pozwolić na zakończenie wywodu mimo istnienia dopasowań.
To, w jaki sposób znajduje się dopasowanie lewej strony musi zostać precyzyjnie określone, bo drobne różnice w definicji dopasowania mają daleko idące konsekwencje.
Dopasowanie przez przesunięcie (translację). Metaforycznie można powiedzieć, że przesuwamy rysunek lewej strony bez przechylania go na boki i staramy się nałożyć wszystkie jego linie na linie rysunku roboczego.
Translacja z ewentualnym skalowaniem. Jeśli lewa strona jest trójkątem równobocznym o wysokości 5 jednostek, to dopasuje się ona do trójkąta równobocznego o wysokości 2 jednostek o ile oba są tak samo zorientowane, np. oba mają jeden bok równoległy do osi X.
Podobieństwo geometryczne, czyli dowolne złożenie translacji, skalowania (formalnie: jednokładności), obrotu i odbicia lustrzanego. Uwaga, jeśli lewa strona jest okręgiem, to przy takiej definicji będzie ona miała nieskończenie wiele dopasowań do każdego okręgu występującego na rysunku.
Wiele innych możliwości, np. zawężone podobieństwo bez odbić lustrzanych albo translacja, skalowanie i obroty o wielokrotność 45°.
Struktury danych reprezentujące rysunki i strony reguł oraz operujące na nich algorytmy znajdowania dopasowań nie są banalne, zwłaszcza gdy przez dopasowanie rozumie się podobieństwo. Trzeba w nich uwzględnić możliwość pojawienia się figur emergentnych (przecięcie dwóch nakładających się figur geometrycznych też jest jakąś figurą; stykające się figury tworzą nową, większą figurę). Nie warto aplikować reguły, jeśli w wyniku jej zastosowania rysunek w oczach użytkownika się nie zmieni (np. reguła z kwadratem po lewej stronie, po prawej ten sam kwadrat z okręgiem w środku — jeśli rysunek zawiera kwadrat z już wcześniej wpisanym okręgiem, to nie warto tej reguły tam dopasowywać).
Dla uproszczenia problemu można użyć markerów. Można o nich myśleć jako o figurach mających unikalny kształt — marker w lewej stronie reguły można dopasować tylko do znajdującego się na rysunku markera tego samego typu. Służą do jawnego wskazywania tych miejsc rysunku, w których chcemy aplikować reguły. W najprostszym przypadku wszystkie reguły gramatyki będą miały po lewej stronie pojedynczy marker, po prawej rysunek i nowe markery w tych punktach, w których chcemy kontynuować wywód.
Oryginalny artykuł o gramatykach kształtu zakładał, że wygenerowane rysunki będą potem dodatkowo kolorowane. Pozwolę sobie pominąć milczeniem to, jak definiowano zasady kolorowania.
Projekt sprawdza, czy umiecie Państwo przełożyć teorię z wykładu na praktykę. Musicie zaimplementować interaktywną aplikację, która pozwala śledzić proces generowania rysunku przy pomocy gramatyki kształtu. Będziecie musieli przy tym podjąć dwie ważne decyzje.
Pierwsza decyzja: jak ma działać „silnik” aplikacji, czyli część kodu odpowiedzialna za stosowanie reguł transformacyjnych. Musicie zdecydować, z jakich prymitywów geometrycznych budowane są rysunki, czy będą wykrywane kształty emergentne, co może być po lewej stronie reguły i jak zdefiniowane jest dopasowanie. Od tego zależy stopień skomplikowania algorytmów, które będziecie musieli opracować. Osobiście radziłbym zrezygnować z kształtów emergentnych, bo one bardzo wszystko utrudniają.
Druga decyzja: jak ma wyglądać GUI aplikacji. Absolutne minimum to okno wyświetlające aktualny stan rysunku roboczego, po kliknięciu w nie myszką (czy też naciśnięciu spacji) wykonuje się losowo wybrana reguła i zawartość okna się uaktualnia. To oczywiście nie pozwala użytkownikowi wybierać, co ma być zrobione jeśli dla danego stanu rysunku jest wiele dopasowań, mam więc nadzieję że opracujecie Państwo bardziej zaawansowane GUI.
W aplikacji nie trzeba implementować edytora gramatyk — to zajęłoby dużo czasu i niczego ciekawego Państwa nie nauczyło. Gramatyki, według których generowane są rysunki proszę zdefiniować „na sztywno” w kodzie źródłowym (dobrze byłoby, aby były co najmniej trzy). GUI powinno umożliwiać wybranie jednej z dostępnych gramatyk i rozpoczęcie wywodu od jej rysunku początkowego.
Projekt oceniany będzie z uwagi na trzy kryteria:
Możliwości silnika reguł (ignorowanie kształtów emergentnych nie obniży oceny projektu).
Jakość GUI, czyli możliwość kontroli wywodu oraz wyraźne pokazywanie użytkownikowi, co reguły robią z rysunkiem.
Poprawność i czytelność kodu źródłowego. Będziecie Państwo musieli mi go pokazać i objaśnić jak działa, a ja będę musiał go zrozumieć.
Na przykład, jeśli aplikacja potrafi dopasowywać tylko poprzez translację i tylko trójkąty (a więc tylko one mogą być po lewej stronie reguł), to piątki nie dostaniecie. Możecie dostać czwórkę, jeśli ma przyzwoite GUI i poprawny kod. Jeśli miałaby wybitne GUI, to może nawet plus czwórkę.
Po przedyskutowaniu ze mną kodu aplikacji trzeba go spakować do archiwum ZIP i wrzucić na Pegaza. Projekt uznaję za zaliczony dopiero wtedy, gdy otrzymam jego kod (i ten kod daje się uruchomić, co powinno być oczywiste ale na wszelki wypadek lepiej to czarno na białym napisać).
Możecie napisać albo tradycyjną aplikację desktopową (np. w Javie, C++ lub Pythonie), albo aplikację działającą w środowisku przeglądarki WWW. Jeśli zdecydujecie się na to drugie, to spróbujcie zrobić to tak, aby można ją było uruchomić po prostu otwierając plik HTML z pendrive’a.
Możecie korzystać z bibliotek do tworzenia rysunków, reprezentowania figur, znajdowania podobieństw geometrycznych itd. Ale jeśli udałoby się Wam znaleźć bibliotekę z procedurami do aplikowania reguł gramatyk kształtu, to z niej nie wolno korzystać — te algorytmy musicie opracować i zaimplementować sami.
Według najprostszej definicji graf składa się z wierzchołków i krawędzi. Wierzchołki przeważnie są etykietowane, czasem również atrybutowane, czyli mające przypisany zbiór par (nazwa atrybutu, wartość atrybutu). Krawędzie też mogą mieć etykiety (często zwane wagami, jeśli są liczbowe) oraz atrybuty, choć jest to rzadziej spotykane niż w przypadku wierzchołków. W zależności od tego, z jakim typem grafu mamy do czynienia, krawędzie mogą być skierowane lub nieskierowane.
Grafy są bardzo wygodnym narzędziem do modelowania wewnętrznej struktury projektowanej rzeczy. Wierzchołki reprezentują jej części składowe, czyli komponenty. Etykiety wierzchołków mówią co to za komponenty, a ewentualne atrybuty określają ich cechy.
Przykład: jeśli projektujemy krzesło, to w jego wstępnym, ogólnym modelu może się pojawić wierzchołek z etykietą „oparcie”. Gdy zaczniemy ten model uszczegóławiać, to możemy:
Krawędzie reprezentują relacje pomiędzy komponentami. Jeśli chcemy modelować kilka różnych relacji, to krawędzie muszą mieć etykiety z nazwami tych relacji. Gdy modelujemy relacje asymetryczne trzeba użyć grafu skierowanego.
Przykład: najprostszą relacją jest przyleganie komponentów. Jest to relacja symetryczna (oparcie „przylega” do siedziska wtedy i tylko wtedy, gdy siedzisko „przylega” do oparcia), można więc ją reprezentować krawędzią nieskierowaną (bez strzałki). Inny przykład: pozioma listwa jest „połączona na czop” z pionowym drążkiem. Rysujemy krawędź skierowaną zaczynającą się w wierzchołku „listwa” i kończącą w wierzchołku „drążek”, bo jest to relacja asymetryczna (drążek nie ma czopu, ma wpust w który wchodzi czop listwy).
Produkcje gramatyk grafowych (zwane również regułami transformacyjnymi) pozwalają modyfikować grafy. W największym skrócie:
Przy próbach wdrożenia powyższej idei pojawiają się komplikacje. Pierwszą jest pytanie, co zrobić z krawędziami, które łączyły usuwaną część z resztą grafu. Nie można ich zostawić, aby sobie dyndały z jednym końcem nie podłączonym do żadnego wierzchołka. Usunąć? Przekierować, czyli przypiąć luźny koniec do któregoś wierzchołka we wstawianej kopii prawej strony? Jeśli tak, to do którego? A może zrobić coś jeszcze innego?
Druga komplikacja dotyczy atrybutów i ich wartości. Załóżmy, że mamy produkcję zastępującą wierzchołek „oparcie” pięcioma wierzchołkami „drążek” i „listwa”. Jeśli oparcie miało „kolor: ciemnobrązowy”, to czy nie należałoby tego koloru skopiować do tych pięciu nowych wierzchołków? Choć z drugiej strony, być może krzesło ładniej by wyglądało gdyby poziome listewki były o ton jaśniejsze, ale wtedy nie jest to już proste kopiowanie. Atrybutu „wysokość” też nie można tak po prostu skopiować — jeśli oparcie miało mieć 50 cm, to drążkom trzeba taką wysokość ustawić, ale wysokość listewek należy albo pozostawić nieokreśloną, albo np. ustawić na losową wartość z przedziału 3–10 cm.
W literaturze można znaleźć wiele propozycji jak tworzyć krawędzie łączące świeżo wstawioną kopię prawej strony z resztą grafu (są to tzw. mechanizmy osadzenia). Zagadnieniu wyliczania wartości atrybutów w świeżo wstawionych wierzchołkach też poświęcono wiele artykułów. Najbardziej elastycznym, choć mało matematycznym rozwiązaniem jest dołączenie do każdej produkcji napisanego specjalnie dla niej algorytmu, który potrafi dodać właściwe krawędzie i / lub atrybuty.
Na wykładzie były omawiane dodatkowe typy grafów: grafy hierarchiczne, hipergrafy, hipergrafy komponentowo-relacyjne itd. Jednym z typów były grafy z bondami, dla których można zdefiniować prosty mechanizm osadzenia.
Wierzchołki w tych grafach mają bondy, czyli miejsca do których dochodzą krawędzie. Krawędź nie łączy się z wierzchołkiem jako takim, lecz z którymś bondem tego wierzchołka. Podczas rysowania grafu bondy rysowane są jako małe kółeczka leżące na brzegu elipsy-wierzchołka. Jeśli mamy do czynienia z grafem skierowanym, to bondy można podzielić na wyjściowe (z nich wychodzą krawędzie) i wejściowe (do tych krawędzie wchodzą).
Bondy odpowiadają określonym miejscom w komponencie. Np. jeśli mamy siedzisko krzesła i nogi krzesła, to możemy chcieć zawrzeć w modelu grafowym informację o tym, w których punktach te nogi są do siedziska przymocowane. Umawiamy się więc, że wierzchołek „siedzisko” będzie miał cztery bondy odpowiadające rogom dolnej strony siedziska i piąty bond odpowiadający punktowi centralnemu. Dzięki tym bondom można łatwo wyrazić strukturę nóg krzesła tradycyjnego oraz nowoczesnego (zdjęcia ze sklepu Halomeble).
Jako projekt będziecie Państwo pisać aplikację generującą plany zespołów ogrodowych / parkowych. Duży ogród można podzielić na części różnych typów (spacerowa, restauracyjna, do gry w golfa itd.), te z kolei zawierają podkomponenty takie jak ścieżki, ławki, kwietniki, basen albo może jeziorko, budynki pomocnicze (szopa ze sprzętem do czyszczenia basenu, toaleta, budka z hotdogami), drzewa oraz krzewy, i co tam jeszcze fantazja projektantowi podpowie.
Dla uproszczenia można przyjąć, że każdy z tych komponentów jest prostokątem, zorientowanym zgodnie ze wskazaniami kompasu. Można wtedy mówić o północnym, południowym itd. brzegu komponentu. Dodajmy wszystkim wierzchołkom po cztery bondy reprezentujące te brzegi. Teraz, jeśli chcemy wyrazić w modelu fakt „basen przylega do kortu tenisowego od jego południowej strony”, rysujemy krawędź łączącą północny bond basenu z południowym bondem kortu.
Rozważmy produkcję używającą grafów z bondami. Dla prostoty załóżmy, że po lewej stronie jest pojedynczy wierzchołek. Bondy tego wierzchołka modelu, który został dopasowany do lewej strony, prawdopodobnie są połączone krawędziami z resztą modelu. Te krawędzie po usunięciu wierzchołka zawisłyby w próżni, więc je też trzeba usunąć. Zamiast nich zostaną stworzone nowe krawędzie.
Mechanizm osadzania świeżo dodanych wierzchołków, czyli łączenia ich krawędziami z resztą modelu, oparty jest o numerki przypisane bondom w lewej i prawej stronie produkcji. Jeśli istniały jakieś krawędzie łączące bond oznaczony numerem 1 z resztą grafu, to po wstawieniu kopii prawej strony dodajemy krawędzie łączące wszystkie jej bondy oznaczone „1” z tymi miejscami w reszcie grafu, do których podłączone były oryginalne krawędzie. To samo robimy dla pozostałych numerów.
Rozważmy produkcję, która zastępuje wierzchołek „obszar restauracyjny” dwoma nowymi wierzchołkami „restauracja” i „parking”. Parking ma być na południe od restauracji, więc po prawej stronie jest krawędź łącząca odpowiednie bondy tych wierzchołków. Teraz pytanie: jakie numerki i którym bondom przypisać? Jeśli coś stykało się z północnym brzegiem obszaru restauracyjnego, to będzie też sąsiadowało z północną ścianą restauracji. Bondy reprezentujące te brzegi dostają numerek „1”. Jeśli coś leżało po wschodniej stronie obszaru, to będzie się stykać ze wschodnimi brzegami restauracji i parkingu. Numerek „2” stawiamy więc przy jednym bondzie po lewej stronie i przy dwóch bondach po prawej stronie. Pozostałe strony świata analizujemy w analogiczny sposób.
Powyższy mechanizm jest matematycznie elegancki, ale w praktycznych zastosowaniach może okazać się niewystarczający. Jeśli obszar restauracyjny miał po wschodniej stronie wielki trawnik i nic innego, to oczywiście ten trawnik będzie sąsiadował i z restauracją, i z parkingiem. Ale jeśli po wschodniej stronie były trzy korty tenisowe, wymodelowane jako trzy odrębne wierzchołki, to kort leżący najbardziej na południe raczej nie będzie stykał się z restauracją, lecz tylko z parkingiem. Mechanizm osadzenia oparty o numerowane bondy nie potrafi rozróżnić tych dwóch sytuacji. Zawsze też traktuje wszystkie wierzchołki oryginalnie podpięte do jednego bondu (u nas: korty tenisowe) w ten sam sposób.
Jako drugi projekt macie Państwo napisać aplikację generującą projekty ogrodów lub zespołów parkowych. Tak samo jak w pierwszym projekcie musicie pokazać, że umiecie przełożyć teorię z wykładu na praktykę. Nie wystarczy wygenerować rysunek — musi być to zrobione za pomocą gramatyki grafowej i interpretacji otrzymanego modelu grafowego.
Zalecam, abyście najpierw rozrysowali sobie na kartkach papieru gramatykę, której aplikacja będzie używała do generowania ogrodów, a dopiero potem zabrali się za kodowanie. Produkcje tej gramatyki mogą być najprostsze z możliwych: po lewej stronie pojedynczy wierzchołek reprezentujący wysokopoziomowy komponent, po prawej uszczegółowienie tego komponentu. Dla większości / wszystkich wysokopoziomowych komponentów musi istnieć po kilka alternatywnych produkcji je rozwijających, bo bez tego gramatyka nie byłaby w stanie generować różnych wersji ogrodu.
Oto kilka hipotetycznych produkcji:
Każdą z powyższych produkcji można zaimplementować jako odrębną procedurę, która jako argument dostaje informację o tym, który wierzchołek grafu powinna przetworzyć. Procedura zastępuje go nowymi wierzchołkami, dodaje krawędzie według potrzeb i wylicza wartości atrybutów dla wstawionych wierzchołków.
Na początku model ogrodu składa się z jednego wierzchołka opatrzonego etykietą „ogród”. Model ten jest rozwijany poprzez stosowanie produkcji. Po pewnym czasie wszystkie etykiety wierzchołków w modelu będą terminalne, tzn. nie będą występować po lewej stronie żadnej produkcji, i wtedy proces generowania modelu się kończy.
O tym, którą produkcję w danym momencie zastosować i do którego wierzchołka może decydować użytkownik aplikacji albo generator liczb pseudolosowych. Możecie swobodnie wybierać, nie będę tego brał pod uwagę podczas oceniania projektu.
Klientom, którzy zamawiają projekty ogrodów nie można pokazywać grafów, bo oni na grafach się nie znają. Trzeba im pokazywać ładne rysunki powstałe w wyniku dokonania interpretacji modeli grafowych, takie jak na obrazach view1.png, view2.png i view3.png, albo jak na przykładach z witryny SmartDraw (ale bez tekstowych opisów).
Jednym ze sposobów tworzenia takich wizualizacji jest przypisanie każdemu wierzchołkowi odpowiedniej reprezentacji graficznej, a następnie połączenie tych wszystkich małych rysunków w jedną całość, czyli w potrzebny nam plan ogrodu. Na przykład, jeśli w modelach generowanych przez gramatykę mogą się pojawić wierzchołki z etykietą „drzewo”, to aplikacja musi umieć na planie ogrodu narysować drzewo. A jeśli gramatyka przewiduje, że „drzewo” może mieć atrybuty „gatunek” (dąb, świerk, brzoza) i „rozmiar” (małe albo duże), to rysunek drzewa powinien mieć warianty odpowiadające możliwym kombinacjom wartości tych atrybutów.
Ustalanie pozycji i rozmiaru rysowanych komponentów jest dość kłopotliwe, jeśli robi się to na podstawie krawędzi reprezentujących przyleganie, albo nawet trochę bardziej precyzyjne „przylega od strony północnej”, „przylega od strony wschodniej” itd. Aby nie trzeba było się męczyć z pisaniem algorytmu, który będzie przydzielał komponentom odpowiednie prostokąty na planie ogrodu z zachowaniem ograniczeń narzuconych przez istniejące krawędzie pozwalam Państwu pójść na łatwiznę i wprowadzić atrybuty przechowujące dane o geometrii komponentów.
Każdy wierzchołek w grafie będzie miał wtedy zapisane współrzędne lewego dolnego rogu prostokąta zajmowanego przez reprezentowany komponent oraz szerokość i wysokość tego prostokąta. Powiedzmy, że damy tym atrybutom nazwy „x”, „y”, „w” i „h”. Jeśli sobie założymy, że ogród ma mieć powierzchnię 30 na 40 metrów, to początkowy wierzchołek „ogród” będzie miał „x: 0”, „y: 0”, „w: 30” i „h: 40”. Jeśli użyjemy produkcji dzielącej ogród na obszar trawiasty i obszar zadrzewiony, to obowiązkiem tej produkcji będzie ustawienie wartości tych czterech atrybutów w świeżo wstawianych wierzchołkach. Powiedzmy, że pierwszemu obszarowi ustawi „x: 0”, „y: 0”, „w: 30” i „h: 25”, a drugiemu „x: 0”, „y: 25”, „w: 30” i „h: 15”. Jak widać, prostokąt ogrodu został podzielony na dwa podprostokąty.
To, w jakiej proporcji i w jakim kierunku (poziomo czy pionowo) produkcja dzieli ogród może być niezmienne, albo może zależeć od wyników zwracanych przez generator liczb pseudolosowych. Użycie RNG wymaga troszeczkę większego wysiłku przy implementowaniu procedury odpowiadającej tej produkcji, ale za to projekty ogrodów generowane przez aplikację będą bardziej różnorodne.
Absolutne minimum, jeśli chodzi o GUI aplikacji, to okno z przyciskiem „Wygeneruj projekt”, po którego naciśnięciu którego w oknie pojawia się finalny rysunek z planem ogrodu. Można też, na wzór pierwszej aplikacji, pokazywać jak projekt wygląda na kolejnych etapach rozwoju — wymaga to opracowania reprezentacji graficznej dla nieterminalnych wierzchołków „ogród”, „obszar kwiatowy” itp.
Aplikacja nie musi wyświetlać grafu. Oczywiście, jeśli ktoś z Państwa zdecyduje to robić to proszę bardzo, ale graf musi być wyświetlany obok jego interpretacji, a nie zamiast.
Tak jak poprzednio możecie napisać tradycyjną aplikację desktopową lub aplikację działającą w środowisku przeglądarki. Wolno korzystać z bibliotek obsługujących grafy, nie wolno z bibliotek implementujących gramatyki grafowe.
Ponieważ grupy są bardzo liczne, oddawane projekty można mi prezentować nie tylko podczas ćwiczeń, ale również w umówionych indywidualnie terminach. Harmonogram tych terminów jest dostępny na Google Sheets, proszę się samodzielnie wpisać na wybrany dzień i godzinę.
Przez „meeting konsultacyjny” rozumieć należy to samo teamsowe spotkanie, na którym mam cotygodniowe konsultacje dla studentów. Odnośnik pozwalający do niego dołączyć jest na mojej stronie w USOSweb.