%%%%
%%%%  Bayesian networks using noisy OR (2) -- alarm_nor_generic.psm
%%%%
%%%%  Copyright (C) 2004,2006,2007,2008
%%%%    Sato Laboratory, Dept. of Computer Science,
%%%%    Tokyo Institute of Technology

%%  This example is borrowed from:
%%    Poole, D., Probabilistic Horn abduction and Bayesian networks,
%%    In Proc. of Artificial Intelligence 64, pp.81-129, 1993.
%%
%%                (Fire)  (Tampering)
%%                /    \   /
%%          ((Smoke)) (Alarm)
%%                       |
%%                   (Leaving)       ((  )) -- observable node
%%                       |            (  )  -- hidden node
%%                   ((Report))
%%
%%  In this network, we assume that all rvs (random variables) take on
%%  {yes,no} and also assume that only two nodes, `Smoke' and `Report', are
%%  observable.
%%
%%  Furthermore, as did in alarm_nor_basic.psm, we consider that the Alarm
%%  variable's CPT given through the noisy-OR rule.  That is, we have the
%%  following inhibition probabilities:
%%
%%      P(Alarm=no | Fire=yes, Tampering=no)  = 0.3
%%      P(Alarm=no | Fire=no,  Tampering=yes) = 0.2
%%
%%  The CPT for the Alarm variable is then constructed from these inhibition
%%  probabilities and the noisy-OR rule:
%%
%%    +------+-----------+--------------------+----------------+
%%    | Fire | Tampering |    P(Alarm=yes)    |  P(Alarm=no)   |
%%    +------+-----------+--------------------+----------------+
%%    | yes  |    yes    | 0.94 = 1 - 0.3*0.2 | 0.06 = 0.3*0.2 |
%%    | yes  |     no    | 0.7  = 1 - 0.3     | 0.3            |
%%    |  no  |    yes    | 0.8  = 1 - 0.2     | 0.2            |
%%    |  no  |     no    | 0                  | 1.0            |
%%    +------+-----------+--------------------+----------------+
%%
%%  While alarm_nor_basic.psm uses network-specific implementation, in this 
%%  program, we attempt to introduce a more generic routine that can handle
%%  noisy OR.  To be more concrete:
%%
%%  - We specify noisy OR nodes in a declarative form (with noisy_or/3).
%%  - We introduce generic probabilistic predicates that make probabilistic
%%    choices, following the specifications of noisy OR nodes. 
%%
%%  The definition of these generic probabilistic predicates are given in
%%  noisy_or.psm, and we will include noisy_or.psm into this program.
%%

%%-------------------------------------
%%  Quick start (the same as those listed in alarm_nor_basic.psm): 
%%
%%  ?- prism(alarm_nor_generic).
%%
%%  Print the CPT of the Alarm variable constructed from the noisy OR rule:
%%  ?- print_dist_al.
%%
%%  Print logical formulas that express the probabilistic behavior of
%%  the noisy OR rule for Alarm:
%%  ?- print_expl_al.
%%
%%  Get the probability and the explanation graph:
%%  ?- prob(world(yes,no)).
%%  ?- probf(world(yes,no)).
%%
%%  Get the most likely explanation and its probability:
%%  ?- viterbif(world(yes,no)).
%%  ?- viterbi(world(yes,no)).
%%
%%  Compute conditional hindsight probabilities:
%%  ?- chindsight(world(yes,no),world(_,_,_,_,_,_)).
%%  ?- chindsight_agg(world(yes,no),world(_,_,query,yes,_,no)).
%%
%%  Learn parameters from randomly generated 100 samples
%%  ?- alarm_learn(100).

%%-------------------------------------
%%  Declarations:

values(_,[yes,no]).

:- include('noisy_or.psm').
      % We include generic probabilistic predicates that can handle
      % noisy-OR.  The following predicates will be available:
      % 
      % - cpt(X,PaVs,V) represents a probabilistic choice where a
      %   random variable X given instantiations PaVs of parents
      %   takes a value V.  If X is an ordinary node, a random
      %   switch bn(X,PaVs) will be used.  On the other hand, if
      %   X is a noisy-OR node, switch cause(X,Y) will be used,
      %   where Y is one of parents of X.
      %
      % - set_nor_params/0 sets inhibition probabilisties (i.e.
      %   the parameters of switches cause(X,Y)) according to
      %   the specifications for noisy-OR nodes with noisy_or/3.

%%------------------------------------
%%  Modeling part:

world(Sm,Re):- world(_,_,_,Sm,_,Re).

world(Fi,Ta,Al,Sm,Le,Re) :-
   cpt(fi,[],Fi),             % P(Fire)
   cpt(ta,[],Ta),             % P(Tampering)
   cpt(sm,[Fi],Sm),           % CPT P(Smoke | Fire)
   cpt(al,[Fi,Ta],Al),        % CPT P(Alarm | Fire,Tampering)
   cpt(le,[Al],Le),           % CPT P(Leaving | Alarm)
   cpt(re,[Le],Re).           % CPT P(Report | Leaving)


% declarations for noisy OR nodes:
noisy_or(al,[fi,ta],[[0.7,0.3],[0.8,0.2]]).

%%------------------------------------
%%  Utility part:

alarm_learn(N) :-
   unfix_sw(_),                  % Make all parameters changeable
   set_params,                   % Set ordinary parameters
   set_nor_params,               % Set inhibition parameters
   get_samples(N,world(_,_),Gs), % Get N samples
   fix_sw(bn(fi,[])),            % Preserve the parameter values
   learn(Gs).                    %   for {msw(bn(fi,[]),yes), msw(bn(fi,[]),no)}

:- set_params.
:- set_nor_params.

set_params:-
   set_sw(bn(fi,[]),[0.1,0.9]),
   set_sw(bn(ta,[]),[0.15,0.85]),
   set_sw(bn(sm,[yes]),[0.95,0.05]),
   set_sw(bn(sm,[no]),[0.05,0.95]),
   set_sw(bn(le,[yes]),[0.88,0.12]),
   set_sw(bn(le,[no]),[0.01,0.99]),
   set_sw(bn(re,[yes]),[0.75,0.25]),
   set_sw(bn(re,[no]),[0.10,0.90]).

%% Check routine for Noisy-OR

print_dist_al:-
   ( member(Fi,[yes,no]),
     member(Ta,[yes,no]),
     member(Al,[yes,no]),
     get_cpt_prob(al,[Fi,Ta],Al,P),
     format("P(al=~w | fi=~w, ta=~w):~t~6f~n",[Al,Fi,Ta,P]),
     fail
   ; true
   ).

print_expl_al:-
   ( member(Fi,[yes,no]),
     member(Ta,[yes,no]),
     member(Al,[yes,no]),
     get_cpt_probf(al,[Fi,Ta],Al),
     fail
   ; true
   ).

%% [Note] prob/1 and probf/1 will fail if its argument fails

get_cpt_prob(X,PaVs,V,P):-
   ( prob(cpt(X,PaVs,V),P)
   ; P = 0.0
   ),!.

get_cpt_probf(X,PaVs,V):-
   ( probf(cpt(X,PaVs,V))
   ; format("cpt(~w,~w,~w): always false~n",[X,PaVs,V])
   ),!.
