Causal optimization and non-causal optimization in a Bayesian network.

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


using BayesServer.Inference;
using BayesServer.Inference.RelevanceTree;
using BayesServer.Optimization;
using BayesServer.Optimization.Genetic;
using BayesServer.Statistics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BayesServer.HelpSamples
{
    public static class CausalOptimizationExample
    {
        public static void Main()
        {
            var network = LoadNetwork();

            // reference some variables and their states
            var gender = network.Variables["Gender", true];
            var genderMale = gender.States["Male", true];

            var drugA = network.Variables["DrugA", true];
            var drugB = network.Variables["DrugB", true];

            var recovered = network.Variables["Recovered", true];
            var recoveredTrue = recovered.States["True", true];

            // the objective here is to maximize the recovery rate of patients
            var objective = new Objective(recoveredTrue, ObjectiveKind.Maximize);

            var optimizer = new GeneticOptimizer();
            var optimizerOptions = new GeneticOptimizerOptions();

            // simplification is optional, but tests whether
            // fewer variables having evidence provides a 'close enough' solution
            var simplify = new GeneticSimplification();
            var simplifyOptions = new GeneticSimplificationOptions();

            {
                // Example 1.  Non-causal optimization
                var designVariables = new List<DesignVariable>
                {
                    new DesignVariable(drugA, 0.0, 1.0, true, InterventionType.None),   // 'None' does not permit interventions
                    new DesignVariable(drugB, 0.0, 1.0, true, InterventionType.None)    // 'None' does not permit interventions
                };

                var output = optimizer.Optimize(
                    network,
                    objective,
                    designVariables,
                    null,
                    optimizerOptions);

                simplifyOptions.EvidenceToSimplify = output.Evidence;

                var outputSimple = simplify.Optimize(
                    network,
                    objective,
                    designVariables,
                    null,
                    simplifyOptions);
                

                Console.WriteLine($"Example 1 (Non-causal), objective = {outputSimple.ObjectiveValue:P2}.");
                Console.WriteLine($"    Drug A = {EvidenceToString(outputSimple.Evidence, drugA)}");
                Console.WriteLine($"    Drug B = {EvidenceToString(outputSimple.Evidence, drugB)}");
                Console.WriteLine($"    Gender = {EvidenceToString(outputSimple.Evidence, gender)}");
            }

            {
                // Example 2.  Causal optimization
                var designVariables = new List<DesignVariable>
                {
                    new DesignVariable(drugA, 0.0, 1.0, true, InterventionType.Do),
                    new DesignVariable(drugB, 0.0, 1.0, true, InterventionType.Do)
                };

                var options = new GeneticOptimizerOptions();

                var output = optimizer.Optimize(
                    network,
                    objective,
                    designVariables,
                    null,
                    options);

                simplifyOptions.EvidenceToSimplify = output.Evidence;

                var outputSimple = simplify.Optimize(
                    network,
                    objective,
                    designVariables,
                    null,
                    simplifyOptions);


                Console.WriteLine($"Example 2 (Causal), objective = {outputSimple.ObjectiveValue:P2}.");
                Console.WriteLine($"    Drug A = {EvidenceToString(outputSimple.Evidence, drugA)}");
                Console.WriteLine($"    Drug B = {EvidenceToString(outputSimple.Evidence, drugB)}");
                Console.WriteLine($"    Gender = {EvidenceToString(outputSimple.Evidence, gender)}");
            }

            {
                // Example 3.  Mixed causal/non-causal optimization

                var designVariables = new List<DesignVariable>
                {
                    new DesignVariable(drugA, 0.0, 1.0, true, InterventionType.Do),
                    new DesignVariable(drugB, 0.0, 1.0, true, InterventionType.Do),
                    new DesignVariable(gender, 0.0, 1.0, true, InterventionType.None),
                };

                var options = new GeneticOptimizerOptions();

                var output = optimizer.Optimize(
                    network,
                    objective,
                    designVariables,
                    null,
                    options);

                simplifyOptions.EvidenceToSimplify = output.Evidence;

                var outputSimple = simplify.Optimize(
                    network,
                    objective,
                    designVariables,
                    null,
                    simplifyOptions);


                Console.WriteLine($"Example 3 (Mixed causal/non-causal), objective = {outputSimple.ObjectiveValue:P2}.");
                Console.WriteLine($"    Drug A = {EvidenceToString(outputSimple.Evidence, drugA)}");
                Console.WriteLine($"    Drug B = {EvidenceToString(outputSimple.Evidence, drugB)}");
                Console.WriteLine($"    Gender = {EvidenceToString(outputSimple.Evidence, gender)}");
            }

            // The following example use fixed evidence, which allow
            // us to explore solutions within specified sub-populations

            {
                // Example 4.  Non-causal optimization with fixed evidence

                var designVariables = new List<DesignVariable>
                {
                    new DesignVariable(drugA, 0.0, 1.0, true, InterventionType.None),
                    new DesignVariable(drugB, 0.0, 1.0, true, InterventionType.None)
                };

                var fixedEvidence = new Evidence(network);
                fixedEvidence.SetState(genderMale);

                var options = new GeneticOptimizerOptions();

                var output = optimizer.Optimize(
                    network,
                    objective,
                    designVariables,
                    fixedEvidence,
                    options);

                simplifyOptions.EvidenceToSimplify = output.Evidence;

                var outputSimple = simplify.Optimize(
                    network,
                    objective,
                    designVariables,
                    fixedEvidence,
                    simplifyOptions);


                Console.WriteLine($"Example 4 (Non-causal, Fixed evidence), objective = {outputSimple.ObjectiveValue:P2}.");
                Console.WriteLine($"    Drug A = {EvidenceToString(outputSimple.Evidence, drugA)}");
                Console.WriteLine($"    Drug B = {EvidenceToString(outputSimple.Evidence, drugB)}");
                Console.WriteLine($"    Gender = {EvidenceToString(outputSimple.Evidence, gender)}");
            }

            {
                // Example 5.  Causal optimization with fixed evidence

                var designVariables = new List<DesignVariable>
                {
                    new DesignVariable(drugA, 0.0, 1.0, true, InterventionType.Do),
                    new DesignVariable(drugB, 0.0, 1.0, true, InterventionType.Do)
                };

                var fixedEvidence = new Evidence(network);
                fixedEvidence.SetState(genderMale);

                var options = new GeneticOptimizerOptions();

                var output = optimizer.Optimize(
                    network,
                    objective,
                    designVariables,
                    fixedEvidence,
                    options);

                simplifyOptions.EvidenceToSimplify = output.Evidence;

                var outputSimple = simplify.Optimize(
                    network,
                    objective,
                    designVariables,
                    fixedEvidence,
                    simplifyOptions);


                Console.WriteLine($"Example 5 (Causal with fixed evidence), objective = {outputSimple.ObjectiveValue:P2}.");
                Console.WriteLine($"    Drug A = {EvidenceToString(outputSimple.Evidence, drugA)}");
                Console.WriteLine($"    Drug B = {EvidenceToString(outputSimple.Evidence, drugB)}");
                Console.WriteLine($"    Gender = {EvidenceToString(outputSimple.Evidence, gender)}");
            }

            // Expected output ...

            // Example 1 (Non-causal), objective = 85.83 %.
            //     Drug A = False
            //     Drug B = False
            //     Gender = <missing>
            // Example 2 (Causal), objective = 87.65 %.
            //     Drug A = Do(True)
            //     Drug B = Do(True)
            //     Gender = <missing>
            // Example 3 (Mixed causal / non-causal), objective = 95.00 %.
            //     Drug A = Do(True)
            //     Drug B = Do(True)
            //     Gender = Male
            // Example 4 (Non - causal, Fixed evidence), objective = 95.00 %.
            //     Drug A = True
            //     Drug B = True
            //     Gender = Male
            // Example 5 (Causal with fixed evidence), objective = 95.00 %.
            //     Drug A = Do(True)
            //     Drug B = Do(True)
            //     Gender = Male


        }


        private static string EvidenceToString(IEvidence evidence, Variable variable)
        {
            var evdTypes = evidence.GetEvidenceTypes(variable);

            if (evdTypes.EvidenceType == EvidenceType.None)
                return "<missing>";

            var text = string.Empty;

            if (variable.ValueType == VariableValueType.Discrete)
            {
                text = variable.States[evidence.GetState(variable).Value].Name;
            }
            else
            {
                text = evidence.Get(variable).Value.ToString();
            }

            if(evdTypes.InterventionType == InterventionType.Do)
            {
                text = "Do(" + text + ")";
            }

            return text;
        }

        private static Network LoadNetwork()
        {
            var network = new Network();

            // you can load a network in a number of different ways

            // a) Load from a file...
            //network.Load(@"C:\ProgramData\Bayes Server 9.3\Sample Networks\Asia.bayes");

            // b) Load from a stream

            // c) Load from a string

            // d) construct manually, which is what we do here

            var genderFemale = new State("Female");
            var genderMale = new State("Male");
            var gender = new Variable("Gender", genderFemale, genderMale);
            network.Nodes.Add(new Node(gender));
            gender.Node.Bounds = new Bounds(404.5, 23.0, 183.5, 102.5);

            var drugAFalse = new State("False");
            var drugATrue = new State("True");
            var drugA = new Variable("DrugA", drugAFalse, drugATrue);
            network.Nodes.Add(new Node(drugA));
            drugA.Node.Bounds = new Bounds(50.5, 136.0, 183.5, 102.5);

            var drugBFalse = new State("False");
            var drugBTrue = new State("True");
            var drugB = new Variable("DrugB", drugBFalse, drugBTrue);
            network.Nodes.Add(new Node(drugB));
            drugB.Node.Bounds = new Bounds(45.5, 340, 183.5, 102.5);

            var recoveredFalse = new State("False");
            var recoveredTrue = new State("True");
            var recovered = new Variable("Recovered", recoveredFalse, recoveredTrue);
            network.Nodes.Add(new Node(recovered));
            recovered.Node.Bounds = new Bounds(665, 240, 183.5, 102.5);

            network.Links.Add(new Link(gender.Node, drugA.Node));
            network.Links.Add(new Link(gender.Node, drugB.Node));
            network.Links.Add(new Link(gender.Node, recovered.Node));
            network.Links.Add(new Link(drugA.Node, recovered.Node));
            network.Links.Add(new Link(drugB.Node, recovered.Node));

            // at this stage you could learn the parameters from data
            // but to keep this example self contained
            // we are specifiying the parameters manually
            {
                var table = gender.Node.NewDistribution().Table;
                table[genderFemale] = 0.49;
                table[genderMale] = 0.51;
                gender.Node.Distribution = table;
            }

            {
                var table = drugA.Node.NewDistribution().Table;

                table[drugAFalse, genderFemale] = 0.233236151603499;
                table[drugATrue, genderFemale] = 0.766763848396501;

                table[drugAFalse, genderMale] = 0.756302521008403;
                table[drugATrue, genderMale] = 0.243697478991597;

                drugA.Node.Distribution = table;
            }

            {
                var table = drugB.Node.NewDistribution().Table;

                table[drugBFalse, genderFemale] = 0.2;
                table[drugBTrue, genderFemale] = 0.8;

                table[drugBFalse, genderMale] = 0.85;
                table[drugBTrue, genderMale] = 0.15;

                drugB.Node.Distribution = table;
            }

            {
                var table = recovered.Node.NewDistribution().Table;
                table[genderFemale, drugAFalse, drugBFalse, recoveredFalse] = 0.31;
                table[genderFemale, drugAFalse, drugBFalse, recoveredTrue] = 0.69;

                table[genderFemale, drugAFalse, drugBTrue, recoveredFalse] = 0.22;
                table[genderFemale, drugAFalse, drugBTrue, recoveredTrue] = 0.78;

                table[genderFemale, drugATrue, drugBFalse, recoveredFalse] = 0.27;
                table[genderFemale, drugATrue, drugBFalse, recoveredTrue] = 0.73;

                table[genderFemale, drugATrue, drugBTrue, recoveredFalse] = 0.2;
                table[genderFemale, drugATrue, drugBTrue, recoveredTrue] = 0.8;

                table[genderMale, drugAFalse, drugBFalse, recoveredFalse] = 0.13;
                table[genderMale, drugAFalse, drugBFalse, recoveredTrue] = 0.87;

                table[genderMale, drugAFalse, drugBTrue, recoveredFalse] = 0.1;
                table[genderMale, drugAFalse, drugBTrue, recoveredTrue] = 0.9;

                table[genderMale, drugATrue, drugBFalse, recoveredFalse] = 0.07;
                table[genderMale, drugATrue, drugBFalse, recoveredTrue] = 0.93;

                table[genderMale, drugATrue, drugBTrue, recoveredFalse] = 0.05;
                table[genderMale, drugATrue, drugBTrue, recoveredTrue] = 0.95;

                recovered.Node.Distribution = table;
            }


            return network;
        }
    }
}