Optymalizacja Datalogu

Adrian Szyndela
8 listopada 2001

1. Wprowadzenie.

Przypomnienie:

Kilka pojęć:

W regułach dopuszczamy obecność symboli funkcyjnych (czyli działamy na rozszerzonym Datalogu).

2. Podstawowe algorytmy obliczania zapytań.

Podstawowe algorytmy obliczania zapytań dzielą się na algorytmy wstępujące (ang. bottom-up) oraz algorytmy zstępujące (ang. top-down). Algorytmy wstępujące budują (niejawnie) drzewo dowodu zaczynając od liści (relacje z EDB) i idąc w górę aż do korzenia (danego zapytania). Algorytmy zstępujące zaczynają od zapytania i rozważają œcieżki, które mogą doprowadzić do sukcesu - dowodu.

2.1 Algorytmy wstępujące

Najprostszym i od razu narzucającym się algorytmem jest algorytm naiwny:

  1. Dla każdej reguły dla predykatu p obliczamy relację P używając poprzednio obliczonych relacji oraz relacji z EDB.
  2. Sumujemy wszystkie relacje dla p - w wyniku otrzymujemy P'.
  3. Porównujemy P z P'. Jeśli P=P' dla wszystkich predykatów, to znaleźliśmy najmniejszy punkt stały - koniec. Jeśli natomiast jakieś P jest podzbiorem P' to powtarzamy proces obliczając kolejne przybliżenie relacji (przy P zastąpionym przez P').
Łatwo zauważyć, że algorytm ten wykonuje sporo niepotrzebnych obliczeń, gdyż każde następne przybliżenie relacji zawiera poprzednie przybliżenie i ewentualnie coś jeszcze. Mniej obliczeń wykonuje algorytm semi-naiwny:
  1. jeśli wszystkie ΔP są puste - koniec.
  2. zamieniamy wszystkie relacje P z IDB przez sumę P z ΔP.
  3. dla każdego predykatu p z IDB obliczamy nową relację różnicową ΔP' dla każdej z reguł różnicowych (czyli z Δp w nagłówku) używając relacji z EDB, bieżących relacji P, oraz bieżących relacji różnicowych. Następnie sumujemy relacje dla każdego z Δp.
  4. dla każdej relacji z IDB obliczamy ΔP = ΔP' - P. Wracamy do (1).
Dopuszczając symbole funkcyjne narażamy się na możliwość zapętlenia się algorytmów - w przypadku, gdy najmniejszy punkt stały operatora bezpośredniej konsekwencji jest nieskończony. Bez symboli funkcyjnych bezpieczne reguły przy skończonej bazie danych gwarantują, że powyższe algorytmy zakończą się.

2.2 Algorytmy zstępujące

Podstawowym algorytmem zstępującym jest SLD-rezolucja, która została omówiona na poprzednim referacie.

2.3 Porównanie algorytmów

Obliczanie zapytań ma za zadanie wyprodukować pewną relację bądź część relacji, która jest potrzebna by odpowiedzieć na zapytanie. W przypadkach zapytań, które potrzebują niepełnych relacji SLD-rezolucja działa przeważnie szybciej niż algorytmy naiwne oraz potrzebuje mniej pamięci. Jest też jednak mniej stabilna - może wpaść w nieskończoną pętlę, podczas gdy algorytmy naiwne kończą się sukcesem. Okazuje się, że przy zastosowaniu najlepszych technik w obu przypadkach metody zstępujące nigdy nie są lepsze od wstępujących.

3. Algorytmy ciekawsze niż podstawowe.

Największymi mankamentami SLD-rezolucji jest chyba możliwość zapętlenia się na dobrych programach oraz niepełność wyników obliczeń w takim wypadku. Drzewa reguł/celów to próba stworzenia metody zstępującej, która nie miałaby tych niepożądanych cech, a zarazem posiadała zalety SLD-rezolucji.

3.1 Drzewa reguł/celów.

Drzewa reguł/celów mają dwa rodzaje węzłów: węzły reguł i węzły celów. Drzewo reguł/celów dla danego zapytania G0 konstruuje się następująco:

  1. Korzeń jest węzłem celu dla zapytania G0.
  2. Jeśli węzeł n jest węzłem celu dla celu G, oraz G zawiera predykat p, to dla każdej reguły r dla p unifikujemy nagłówek r z G. Jeśli operacja się powiedzie, tworzymy węzeł reguły dla zmodyfikowanej wersji r, która staje się synem n.
  3. Synami węzła reguły są węzły celów odpowiadające poszczególnym podcelom reguły.
Podstawowy algorytm polega na przechodzeniu drzewa od korzenia do liści (w głąb). Drzewo to nie jest jawnie konstruowane jako struktura danych a jedynie jest wyobrażeniem ścieżki algorytmu, na który składają się dwie wzajemnie rekurencyjne procedury (jedna dla węzłów reguł, druga dla węzłów celów). Algorytm ten jako że jest ideowo niemal identyczny z SLD-rezolucją posiada wszystkie jej wady i zalety. Łatwo poprawić ten algorytm przechodząc drzewo wszerz - jest to wersja z zastosowaniem kolejki (QRGT). Znika wtedy problem niepełności obliczeń. Pojawia się jednak inny problem: nie wiadomo, w którym momencie odpowiedź jest już pełna.

3.2 Grafy reguł/celów.

Grafy reguł/celów służą reprezentacji zbioru klauzul Horna. W porównaniu do drzew reguł/celów nie mają struktury warstwowej oraz posiadają dodatkowe elementy: szablony związania:

Konstrukcja grafu:
  1. węzeł celu z predykatem z EDB nie ma następników;
  2. węzeł celu z predykatem p z IDB posiadającym szablon związania a ma następniki odpowiadające wszystkim regułom dla p. Jeśli r jest taką regułą, to pa ma następnik:
    gdzie X1,...,Xn to wszystkie zmienne występujące w związanych argumentach nagłówka r według szablonu a, a Y1,...,Ym to pozostałe zmienne.
  3. Rozważmy węzeł reguły ri[X1,...,Xn|Y1,...,Ym], i >= 0. Niech q(t1,...,tk) będzie (i+1)-szym podcelem r.
    1. Pierwszym następnikiem jest węzeł celu qb. b jest szablonem, który oznacza j-ty argument z q jako związany jeśli wszystkie zmienne w tj są zwišzane (X1,...,Xn). W przeciwnym przypadku j-ty argument jest oznaczony jako wolny.
    2. Jeśli i+1 jest mniejsze od liczby podcelów w r (czyli q(t1,...,tk) nie jest ostatnim podcelem w r) to następnikiem jest węzeł:
        ri+1[X1,...,Xn,U1,...,Uj|V1,...,Vl]
      gdzie U1,...,Uj są zmiennymi spośród Y1,...,Ym, które występują w q, a V1,...,Vl są pozostałymi zmiennymi spośród Y1,...,Ym.

3.2.1. Unikalne szablony związania.

Korzystając z grafu reguł/celów łatwo wskazać szablony związania, z którymi poszczególne predykaty są używane. Mając tę informację możemy "rozdzielić" predykaty tak, aby każdy miał unikalny szablon związania:

Jedynym problemem mogą być "aliasy", tzn. dwie zmienne w rzeczywistości oznaczające jedną. Oczyszczanie podcelów to metoda eliminująca aliasy. Powiemy, że podcel jest oczyszczony jeśli każdy z jego argumentów jest inną zmienną. Algorytm oczyszczania podcelów wygląda następująco:
  1. Tworzymy nowy predykat p'(Y1,...,Ym) i podstawiamy go w miejsce p(X1,...,Xk) - Y1,...,Ym to wszystkie zmienne występujące pośród X1,...,Xk (m<k).
  2. Dla reguł dla p tworzymy odpowiadające reguły dla p' wykonując odpowiednie podstawienia analogicznie jak w poniższym przykładzie.
Przykład.
Zestaw reguł do oczyszczenia:
r1: p(X,Y) <- s(X,Y).
r2: p(X,Y) <- q(X,W), p(Y,Y).
Zgodnie z krokiem (1) tworzymy nowy predykat p' i wstawiamy go w miejsce p w drugim podcelu reguły r2.
r2: p(X,Y) <- q(X,W), p'(Y).
Reguły dla p' wyglądają następująco:
r3: p'(Y) <- s(Y,Y).
r4: p'(Y) <- q(Y,W), p'(Y).
Reguła r1 pozostaje bez zmian.

4. Algorytm magic-set.

Algorytm magic-set służy do przekształcenia reguł programu w nowy zbiór reguł, lecz dający tę samą odpowiedź na zapytanie. Algorytm oczekuje na wejściu zbioru reguł o unikalnych szablonach związania.

4.1 Algorytm przekształcania reguł.

Następnie tworzymy reguły dla nowych predykatów, oraz nowe reguły dla predykatów z IDB:
  1. Reguły dla predykatów "magicznych". Rozważamy reguły zawierające w ciele predykat p z IDB. Niech p występuje w rj w i-tym podcelu postaci p(t1,...,tn), argumentami (i-1)-szej relacji uzupełniającej są zmienne U1,...,Ul, oraz związanymi argumentami z p według szablonu powiązań są argumenty i1,...,im. Tworzymy regułę:
  2. Reguły dla zerowych predykatów uzupełniających. Niech nagłówek reguły rj postaci p(t1,...,tn) ma związane argumenty i1,...,im. Niech X1,...,Xl będą zmiennymi występującymi wśród ti1,...,tim. Tworzymy regułę:
      supj.0(X1,...,Xl) <- m_p(ti1,...,ti1,...,tim).
  3. Reguły dla pozostałych predykatów uzupełniających. Dla każdej reguły rj o k podcelach i dla każdego i=1,2,...,k-1 tworzymy regułę definiującą supj.i. Niech zmiennymi - atrybutami (i-1)-szej relacji uzupełniającej dla rj będą U1,...,Ul oraz niech zmiennymi - atrybutami i-tej relacji uzupełniającej będą V1,...,Vm. Niech p(t1,...,tn) będzie i-tym podcelem rj. Tworzymy regułę:
      supj.i(V1,...,Vm) <- supj.i-1(U1,...,Ul),p(t1,...,tn).
  4. Reguły dla predykatów z IDB. Niech reguła rj ma k>=1 podcelów, k-tym podcelem będzie p(v1,...,vm), U1,...,Ul zmiennymi - atrybutami (k-1)-szej relacji uzupełniającej, a h(t1,...,tn) nagłówkiem rj. Tworzymy predykat:
      h(t1,...,tn) <- supj.k-1(U1,...,Ul),p(v1,...,vm).

    W przypadku, gdy k=0 predykat ma postać:
      h(t1,...,tn) <- supj.0(U1,...,Ul).
  5. Reguła inicjująca. Niech q(s1,...,sn) będzie danym zapytaniem oraz i1,...,im będą związanymi argumentami zapytania. Tworzymy nowe zapytanie:
      <- m_q(si1,...,sim).
Przykład.
Program wejściowy:
r1: sg(X,Y) <- person(X).
r2: sg(X,Y) <- par(X,Xp), sg(Xp,Yp), par(Y,Yp).
Zapytanie: <- sg(a,W).
Reguły magic-set:
GRUPA 1:
(1) m_sg(Xp) <- sup2.1(X,Xp).
GRUPA 2:
(2) sup1.0(X) <- m_sg(X).
(3) sup2.0(X) <- m_sg(X).
GRUPA 3:
(4) sup2.1(X,Xp) <- sup2.0(X), par(X,Xp).
(5) sup2.2(X,Yp) <- sup2.1(X,Xp), sg(Xp, Yp).
GRUPA 4:
(6) sg(X,X) <- sup1.0(X), person(X).
(7) sg(X,Y) <- sup2.2(X,Yp), par(Y,Yp).
GRUPA 5:
(8) <- m_sg(a).

Po wygenerowaniu powyższego zbioru reguł możemy zastosować algorytm semi-naiwny do obliczeń. Obliczenia wykonują się w czasie proporcjonalnym do czasu wykonania algorytmu QRGT. Ma on jednak tę zaletę, że w przeciwieństwie do QRGT łatwo wykryć pełność odpowiedzi - gdy w kolejnym kroku nie wyliczą się żadne nowe fakty.

4.2 Uproszczenie reguł algorytmu magic-set.

Jeśli reguły dla pewnego predykatu p z IDB nie zawierają w ciele p, to można każdy podcel zawierający p zastąpić przez podstawienie w jego miejsce ciała reguł dla p. Nowe reguły mogą być mniej ogólne od starych, lecz relacje pozostają takie same.

Przykład.
r1: p(f(X),Y) <- t(X,Y).
r2: p(X,a) <- u(X), v(X).
r3: q(X,Y) <- p(X,Z), s(Z,Y).

t(r3): q(f(U),Y) <- p(f(U),V), s(V,Y).
t(r1): p(f(U),V) <- t(U,V).

r4: q(f(U),Y) <- t(U,V), s(V,Y).
r5: q(U,Y) <- u(U), v(U), s(a,Y).

Eliminacja reguł dla p ma jednak sens tylko wtedy gdy:
  1. jest tylko jedna reguła dla p i p występuje tylko w jednym podcelu lub.
  2. jest tylko jedna reguła dla p i w ciele tej reguły jest tylko jeden podcel.
W przypadku zbioru reguł magic-set uproszczeniu podlegają:
  1. zerowe predykaty uzupełniające (po jednej regule długości 1).
  2. predykaty uzupełniające supi.j dla i-tej reguły, jeśli j-tym podcelem jest predykat z EDB.
Często można też uprościć inne predykaty.

4.3 Uogólniony algorytm magic-set.

Dużą zaletą metod zstępujących jest to, że potrafią liczyć dokładnie to, co trzeba aby osiągnąć cel (dowód). Na przykład cel p(X,X,3) mówi, że dwa pierwsze argumenty są równe. SLD-rezolucja potrafi to wykorzystać, natomiast algorytm magic-set nie potrafi. Uogólniony algorytm magic-set przechowuje informacje nie tylko o argumentach związanych, ale o wszystkich. To wystarczy aby wykorzystać dodatkowe właściwości argumentów. Pojawia się jednak problem: niektóre "magiczne" reguły przestają być bezpieczne przy zapytaniach zawierających zmienne. Można rozwiązać ten problem dość łatwo wstawiając zmienne do krotek. Trzeba przy tym uważać na sytuacje, w których dwie krotki z różnymi zmiennymi tak naprawdę oznaczają tę samą krotkę (np. (A, 3) i (B, 3)), lub jedna jest bardziej ogólna (np. (A, A, 3) i (B, C, 3)).

5. Bibliografia.

Jeffrey D. Ullman, "Principles of Database and Knowledge-Base Systems, Volume II", Computer Science Press 1989, rozdziały 12 i 13 (str. 734-876)