Construction & inference in Matlab


% There are a number of ways you can tell Matlab about the Bayes Server API
% Here is one way.  For more information please see the Matlab Java help
bayesVersion = '7.24';   % TODO change to the version you are using
networkPath = ['C:\ProgramData\Bayes Server ', bayesVersion, '\Sample Networks\Waste.bayes'];   % TODO update to the path of the Bayes Server sample network Waste.bayes
jarPath = ['C:\Program Files\Bayes Server\Bayes Server ', bayesVersion, '\API\Java\BayesServer-', bayesVersion, '.jar'];    % TODO update to the path of the Bayes Server jar
javaaddpath(jarPath);


import com.bayesserver.*;
import com.bayesserver.inference.*;

% For licensed software, uncomment the following line, and replace the text with your license key
% License.validate('license-key-goes-here');

% Create a new network
network = Network('Demo');

% In this example we programatically create a simple Bayesian network.
% Note that you can automatically define nodes from data using
% classes in BayesServer.Data.Discovery,
% and you can automatically learn the parameters using classes in
% BayesServer.Learning.Parameters,
% however here we build a Bayesian network from scratch.

% add the nodes (variables)

aTrue = State('True');
aFalse = State('False');

a = Node('A', toJavaArray([aTrue, aFalse]));

bTrue = State('True');
bFalse = State('False');
b = Node('B', toJavaArray([bTrue, bFalse]));

cTrue = State('True');
cFalse = State('False');
c = Node('C', toJavaArray([cTrue, cFalse]));

dTrue = State('True');
dFalse = State('False');
d = Node('D', toJavaArray([dTrue, dFalse]));

network.getNodes().add(a);
network.getNodes().add(b);
network.getNodes().add(c);
network.getNodes().add(d);

% add some directed links

network.getLinks().add(Link(a, b));
network.getLinks().add(Link(a, c));
network.getLinks().add(Link(b, d));
network.getLinks().add(Link(c, d));

% at this point we have fully specified the structural (graphical) specification of the Bayesian Network.

% We must define the necessary probability distributions for each node.

% Each node in a Bayesian Network requires a probability distribution conditioned on it's parents.

% NewDistribution() can be called on a Node to create the appropriate probability distribution for a node
% or it can be created manually.

% The interface 'Distribution' has been designed to represent both discrete and continuous variables,

% As we are currently dealing with discrete distributions, we will use the
% Table class.

% To access the discrete part of a distribution, we use Distribution.Table.

% The Table class is used to define distributions over a number of discrete variables.

tableA = a.newDistribution().getTable();     % access the table property of the Distribution

% IMPORTANT
% Note that calling Node.newDistribution() does NOT assign the distribution to the node.
% A distribution cannot be assigned to a node until it is correctly specified.
% If a distribution becomes invalid  (e.g. a parent node is added), it is automatically set to null.

% as node A has no parents there is no ambiguity about the order of variables in the distribution
tableA.set(0.1, toJavaArray(aTrue));
tableA.set(0.9, toJavaArray(aFalse));

% now tableA is correctly specified we can assign it to Node A;
a.setDistribution(tableA);


% node B has node A as a parent, therefore its distribution will be P(B|A)

tableB = b.newDistribution().getTable();
tableB.set(0.2, toJavaArray([aTrue, bTrue]));
tableB.set(0.8, toJavaArray([aTrue, bFalse]));
tableB.set(0.15, toJavaArray([aFalse, bTrue]));
tableB.set(0.85, toJavaArray([aFalse, bFalse]));
b.setDistribution(tableB);


% specify P(C|A)
tableC = c.newDistribution().getTable();
tableC.set(0.3, toJavaArray([aTrue, cTrue]));
tableC.set(0.7, toJavaArray([aTrue, cFalse]));
tableC.set(0.4, toJavaArray([aFalse, cTrue]));
tableC.set(0.6, toJavaArray([aFalse, cFalse]));
c.setDistribution(tableC);


% specify P(D|B,C)
tableD = d.newDistribution().getTable();

% we could specify the values individually as above, or we can use a TableIterator as follows
iteratorD = TableIterator(tableD, toJavaArray([b, c, d]));
iteratorD.copyFrom([0.4, 0.6, 0.55, 0.45, 0.32, 0.68, 0.01, 0.99]);
d.setDistribution(tableD);


% The network is now fully specified

% If required the network can be saved...

if false   % change this to true to save the network
    
    network.save('fileName.bayes');  % replace 'fileName.bayes' with your own path

end

% Now we will calculate P(A|D=True), i.e. the probability of A given the evidence that D is true

% use the factory design pattern to create the necessary inference related objects
factory = RelevanceTreeInferenceFactory();
inference = factory.createInferenceEngine(network);
queryOptions = factory.createQueryOptions();
queryOutput = factory.createQueryOutput();

% we could have created these objects explicitly instead, but as the number of algorithms grows
% this makes it easier to switch between them

inference.getEvidence().setState(dTrue);  % set D = True

queryA = Table(a);
inference.getQueryDistributions().add(queryA);
inference.query(queryOptions, queryOutput); % note that this can raise an exception (see help for details)

disp(['P(A|D=True) = {', num2str(queryA.get(toJavaArray(aTrue))), ',', num2str(queryA.get(toJavaArray(aFalse))), '}.']);

% Expected output ...
% P(A|D=True) = {0.0980748663101604,0.90192513368984}

% to perform another query we reuse all the objects

% now lets calculate P(A|D=True, C=True)
inference.getEvidence().setState(cTrue);

% we will also return the log-likelihood of the case
queryOptions.setLogLikelihood(true); % only request the log-likelihood if you really need it, as extra computation is involved

inference.query(queryOptions, queryOutput);

queryATrue = queryA.get(toJavaArray(aTrue));
queryAFalse = queryA.get(toJavaArray(aFalse));
logLikelihood = double(queryOutput.getLogLikelihood());

disp(['P(A|D=True, C=True) = ', num2str(queryATrue), ',', num2str(queryAFalse), ', log-likelihood = ', num2str(logLikelihood)]);

% Expected output ...
% P(A|D=True, C=True) = {0.0777777777777778,0.922222222222222}, log-likelihood = -2.04330249506396.


% Note that we can also calculate joint queries such as P(A,B|D=True,C=True)