% Memoize scoped family projection and recursive labels; cousin derivation asks
% for the same generation and branch facts many times.
% Output declarations: query/1 selects the relations written to this example's golden output.
query(generation(X0, X1)).
query(branch(X0, X1)).
query(cousin(X0, X1)).

% The family tree is scoped inside family_graph/2.  family_statement/3 projects
% only the parent and seedBranch facts that the cousin rules need.

% Family-cousins derivation adapted from Eyeling family-cousins.n3.
% Generation numbers are derived from parent links; branch labels distinguish
% descendants of Bob from descendants of Carol.
% The family tree and seed branch labels are quoted as a small formula term, so
% the rules derive from scoped family data rather than global relationship facts.

family_graph(familyGraph, (
  parent(adam, bob),
  parent(adam, carol),
  parent(bob, dave),
  parent(bob, eve),
  parent(carol, frank),
  parent(carol, grace),
  parent(dave, heidi),
  parent(eve, ivan),
  parent(frank, judy),
  seedBranch(dave, b),
  seedBranch(eve, b),
  seedBranch(frank, c),
  seedBranch(grace, c)
)).

% generation/2 walks parent links from Adam, branch/2 propagates seed labels,
% and cousin/2 combines equal generation with different branches.
family_statement(S, P, O) :- family_graph(familyGraph, Context), holds(Context, P, [S, O]).

parent(Parent, Child) :- family_statement(Parent, parent, Child).
branch(Person, Branch) :- family_statement(Person, seedBranch, Branch).

different(b, c).
different(c, b).

generation(adam, 0).
generation(Child, Next) :-
  parent(Parent, Child),
  generation(Parent, Gen),
  add(Gen, 1, Next).

branch(Child, Branch) :-
  parent(Parent, Child),
  branch(Parent, Branch).

derived_branch(Child, Branch) :-
  parent(Parent, Child),
  branch(Parent, Branch).

cousin(X, Y) :-
  generation(X, Gen),
  generation(Y, Gen),
  branch(X, Bx),
  branch(Y, By),
  different(Bx, By).

branch(Person, Branch) :- derived_branch(Person, Branch).
