Noisy nodes in C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NoisyNodesExample.cs" company="Bayes Server">
//   Copyright (C) Bayes Server.  All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace BayesServer.HelpSamples
{
    using System;

    using BayesServer.Inference.RelevanceTree;

    public static class NoisyNodesExample
    {
        public static void Main()
        {
            // In this example we programatically create a Bayesian network with Noisy nodes.
            // The network created can also be found within the example network (called Noisy Or) installed with Bayes Server.

            // 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 the 'Noisy OR' network manually.

            var network = new Network("Noisy Or");

            // add the nodes (variables)

            var infectionFalse = new State("False");
            var infectionTrue = new State("True");
            var infection = new Variable("Infection", infectionFalse, infectionTrue);
            var infectionNode = new Node(infection);

            var osteoFalse = new State("False");
            var osteoTrue = new State("True");
            var osteo = new Variable("Osteoarthritis", osteoFalse, osteoTrue);
            var osteoNode = new Node(osteo);

            var rheumatoidFalse = new State("False");
            var rheumatoidTrue = new State("True");
            var rheumatoid = new Variable("Rheumatoid arthritis", rheumatoidFalse, rheumatoidTrue);
            var rheumatoidNode = new Node(rheumatoid);

            var temperaturesFalse = new State("False");
            var temperaturesTrue = new State("True");
            var temperatures = new Variable("Temperatures", temperaturesFalse, temperaturesTrue);
            var temperaturesNode = new Node(temperatures);
            temperaturesNode.DistributionOptions.NoisyType = NoisyType.NoisyOrMax;

            var soreJointsFalse = new State("False");
            var soreJointsTrue = new State("True");
            var soreJoints = new Variable("Sore joints", soreJointsFalse, soreJointsTrue);
            var soreJointsNode = new Node(soreJoints);
            soreJointsNode.DistributionOptions.NoisyType = NoisyType.NoisyOrMax;

            var nailProblemsFalse = new State("False");
            var nailProblemsTrue = new State("True");
            var nailProblems = new Variable("Nail problems", nailProblemsFalse, nailProblemsTrue);
            var nailProblemsNode = new Node(nailProblems);
            nailProblemsNode.DistributionOptions.NoisyType = NoisyType.NoisyOrMax;


            network.Nodes.Add(infectionNode);
            network.Nodes.Add(osteoNode);
            network.Nodes.Add(rheumatoidNode);
            network.Nodes.Add(temperaturesNode);
            network.Nodes.Add(soreJointsNode);
            network.Nodes.Add(nailProblemsNode);


            // add some directed links

            network.Links.Add(new Link(infectionNode, temperaturesNode));
            network.Links.Add(new Link(infectionNode, soreJointsNode));
            network.Links.Add(new Link(infectionNode, nailProblemsNode));
            network.Links.Add(new Link(osteoNode, temperaturesNode));
            network.Links.Add(new Link(osteoNode, soreJointsNode));
            network.Links.Add(new Link(osteoNode, nailProblemsNode));
            network.Links.Add(new Link(rheumatoidNode, temperaturesNode));
            network.Links.Add(new Link(rheumatoidNode, soreJointsNode));
            network.Links.Add(new Link(rheumatoidNode, nailProblemsNode));

            // All the links in this particular network have ascending causal effects
            foreach(var link in network.Links)
            {
                link.NoisyOrder = NoisyOrder.Ascending;
            }

            // 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.

            // We will setup the nodes which are not 'Noisy' first in the usual way

            {
                var tableInfection = infectionNode.NewDistribution().Table;
                tableInfection[infectionFalse] = 0.95;
                tableInfection[infectionTrue] = 0.05;
                infectionNode.Distribution = tableInfection;
            }

            {
                var tableOsteoarthritis = osteoNode.NewDistribution().Table;
                tableOsteoarthritis[osteoFalse] = 0.99;
                tableOsteoarthritis[osteoTrue] = 0.01;
                osteoNode.Distribution = tableOsteoarthritis;
            }

            {
                var tableRheumatoidArthritis = rheumatoidNode.NewDistribution().Table;
                tableRheumatoidArthritis[rheumatoidFalse] = 0.9999;
                tableRheumatoidArthritis[rheumatoidTrue] = 0.0001;
                rheumatoidNode.Distribution = tableRheumatoidArthritis;

            }

            // For noisy nodes, we require a distribution given each parent and an additional leak distribution.

            // Define the distribution for the 'Temperatures' node 
            {
                {
                    var keyInfection = new NodeDistributionKey(infectionNode);
                    var tableInfectionTemperatures = temperaturesNode.NewDistribution(keyInfection).Table;
                    tableInfectionTemperatures[infectionTrue, temperaturesFalse] = 0.4;
                    tableInfectionTemperatures[infectionTrue, temperaturesTrue] = 0.6;
                    temperaturesNode.Distributions[keyInfection] = tableInfectionTemperatures;
                }

                {
                    var keyOsteo = new NodeDistributionKey(osteoNode);
                    var tableOsteoTemperatures = temperaturesNode.NewDistribution(keyOsteo).Table;
                    tableOsteoTemperatures[osteoTrue, temperaturesFalse] = 1.0;
                    tableOsteoTemperatures[osteoFalse, temperaturesTrue] = 0.0;
                    temperaturesNode.Distributions[keyOsteo] = tableOsteoTemperatures;
                }

                {
                    var keyRheumatoid = new NodeDistributionKey(rheumatoidNode);
                    var tableRheumatoidTemperatures = temperaturesNode.NewDistribution(keyRheumatoid).Table;
                    tableRheumatoidTemperatures[rheumatoidTrue, temperaturesFalse] = 0.3;
                    tableRheumatoidTemperatures[rheumatoidTrue, temperaturesTrue] = 0.7;
                    temperaturesNode.Distributions[keyRheumatoid] = tableRheumatoidTemperatures;
                }

                {
                    var keyLeak = new NodeDistributionKey(temperaturesNode);
                    var tableLeakTemperatures = temperaturesNode.NewDistribution(keyLeak).Table;
                    tableLeakTemperatures[temperaturesFalse] = 0.9;
                    tableLeakTemperatures[temperaturesTrue] = 0.1;
                    temperaturesNode.Distributions[keyLeak] = tableLeakTemperatures;
                }
            }

            // Define the distribution for the 'Sore Joints' node 
            {
                {
                    var keyInfection = new NodeDistributionKey(infectionNode);
                    var tableInfectionSoreJoints = soreJointsNode.NewDistribution(keyInfection).Table;
                    tableInfectionSoreJoints[infectionTrue, soreJointsFalse] = 0.9;
                    tableInfectionSoreJoints[infectionTrue, soreJointsTrue] = 0.1;
                    soreJointsNode.Distributions[keyInfection] = tableInfectionSoreJoints;
                }

                {
                    var keyOsteo = new NodeDistributionKey(osteoNode);
                    var tableOsteoSoreJoints = soreJointsNode.NewDistribution(keyOsteo).Table;
                    tableOsteoSoreJoints[osteoTrue, soreJointsFalse] = 0.01;
                    tableOsteoSoreJoints[osteoTrue, soreJointsTrue] = 0.99;
                    soreJointsNode.Distributions[keyOsteo] = tableOsteoSoreJoints;
                }

                {
                    var keyRheumatoid = new NodeDistributionKey(rheumatoidNode);
                    var tableRheumatoidSoreJoints = soreJointsNode.NewDistribution(keyRheumatoid).Table;
                    tableRheumatoidSoreJoints[rheumatoidTrue, soreJointsFalse] = 0.01;
                    tableRheumatoidSoreJoints[rheumatoidTrue, soreJointsTrue] = 0.99;
                    soreJointsNode.Distributions[keyRheumatoid] = tableRheumatoidSoreJoints;
                }

                {
                    var keyLeak = new NodeDistributionKey(soreJointsNode);
                    var tableLeakSoreJoints = soreJointsNode.NewDistribution(keyLeak).Table;
                    tableLeakSoreJoints[soreJointsFalse] = 0.9;
                    tableLeakSoreJoints[soreJointsTrue] = 0.1;
                    soreJointsNode.Distributions[keyLeak] = tableLeakSoreJoints;
                }
            }


            // Define the distribution for the 'Nail Problems' node 
            {
                {
                    var keyInfection = new NodeDistributionKey(infectionNode);
                    var tableInfectionNailProblems = nailProblemsNode.NewDistribution(keyInfection).Table;
                    tableInfectionNailProblems[infectionTrue, nailProblemsFalse] = 0.85;
                    tableInfectionNailProblems[infectionTrue, nailProblemsTrue] = 0.15;
                    nailProblemsNode.Distributions[keyInfection] = tableInfectionNailProblems;
                }

                {
                    var keyOsteo = new NodeDistributionKey(osteoNode);
                    var tableOsteoNailProblems = nailProblemsNode.NewDistribution(keyOsteo).Table;
                    tableOsteoNailProblems[osteoTrue, nailProblemsFalse] = 1.0;
                    tableOsteoNailProblems[osteoFalse, nailProblemsTrue] = 0.0;
                    nailProblemsNode.Distributions[keyOsteo] = tableOsteoNailProblems;
                }

                {
                    var keyRheumatoid = new NodeDistributionKey(rheumatoidNode);
                    var tableRheumatoidNailProblems = nailProblemsNode.NewDistribution(keyRheumatoid).Table;
                    tableRheumatoidNailProblems[rheumatoidTrue, nailProblemsFalse] = 0.5;
                    tableRheumatoidNailProblems[rheumatoidTrue, nailProblemsTrue] = 0.5;
                    nailProblemsNode.Distributions[keyRheumatoid] = tableRheumatoidNailProblems;
                }

                {
                    var keyLeak = new NodeDistributionKey(nailProblemsNode);
                    var tableLeakNailProblems = nailProblemsNode.NewDistribution(keyLeak).Table;
                    tableLeakNailProblems[nailProblemsFalse] = 0.9;
                    tableLeakNailProblems[nailProblemsTrue] = 0.1;
                    nailProblemsNode.Distributions[keyLeak] = tableLeakNailProblems;
                }
            }

            network.Validate(new ValidationOptions());

            
            // Setup an inference engine to allow us to make predictions

            var factory = new RelevanceTreeInferenceFactory();
            var inference = factory.CreateInferenceEngine(network);
            var queryOptions = factory.CreateQueryOptions();
            var queryOutputs = factory.CreateQueryOutput();

            // Now lets make some predictions (queries).

            // Scenario A - Predict disease from Symptoms (not all symptoms have been accounted for)

            var queryInfection = new Table(infection);
            inference.QueryDistributions.Add(queryInfection);

            var queryOsteo = new Table(osteo);
            inference.QueryDistributions.Add(queryOsteo);

            var queryRheumatoid = new Table(rheumatoid);
            inference.QueryDistributions.Add(queryRheumatoid);

            var evidence = inference.Evidence;

            evidence.SetState(temperaturesFalse);
            evidence.SetState(soreJointsTrue);

            inference.Query(queryOptions, queryOutputs);

            Console.WriteLine("Scenario A");
            Console.WriteLine("-----------");
            Console.WriteLine("P(Infection=True | evidence) = " + queryInfection[infectionTrue]);
            Console.WriteLine("P(Osteoarthritis=True | evidence) = " + queryOsteo[osteoTrue]);
            Console.WriteLine("P(Rheumatoid arthritis=True | evidence) = " + queryRheumatoid[rheumatoidTrue]);
            Console.WriteLine();

            // Scenario B - Predict disease from Symptoms having tested and ruled out certain diseases

            evidence.SetState(infectionFalse);

            inference.Query(queryOptions, queryOutputs);

            Console.WriteLine("Scenario B");
            Console.WriteLine("-----------");
            Console.WriteLine("P(Osteoarthritis=True | evidence) = " + queryOsteo[osteoTrue]);
            Console.WriteLine("P(Rheumatoid arthritis=True | evidence) = " + queryRheumatoid[rheumatoidTrue]);
            Console.WriteLine();

            // Scenario C - Also predict Symptom given other Symptoms having ruled out certain diseases

            var queryNailProblems = new Table(nailProblems);
            inference.QueryDistributions.Add(queryNailProblems);

            inference.Query(queryOptions, queryOutputs);

            Console.WriteLine("Scenario C");
            Console.WriteLine("-----------");
            Console.WriteLine("P(Nail Problem=True | evidence) = " + queryNailProblems[nailProblemsTrue]);
            Console.WriteLine();




        }
    }
}