Skip to main content

Custom evidence reader (Json) C#

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

namespace BayesServer.HelpSamples
{
using BayesServer.Data;
using BayesServer.Inference;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

/// <summary>
/// An example of creating a custom implementation of IEvidenceReaderCommand and IEvidenceReader for reading Json.
/// </summary>
/// <remarks>
/// This example makes use of the Json.Net package (http://www.newtonsoft.com/json) which can be installed via NuGet.
/// </remarks>
public static class JsonReaderExample
{
private const string HairLengthName = "Hair Length";
private const string HeightName = "Height";

static void Main(string[] args)
{
var network = new Network();

// TODO download the network from the Bayes Server User Interface (or Bayes Server Online)
// and adjust the following path
network.Load(@"IdentificationExtended.bayes");

JsonStringExample(network);
JsonStreamExample(network);
}

/// <summary>
/// A class to hold a few properties read from Json. See Json.Net package for more information.
/// </summary>
/// <remarks>
/// Json.NET also supports the dynamic keyword which can be used instead of defining classes like this.
/// </remarks>
private sealed class Person
{
public string HairLength
{
get; set;
}

public double Height
{
get; set;
}
}

/// <summary>
/// A simple helper class that caches references to variables of interest and sets evidence on them.
/// </summary>
private sealed class JsonEvidenceMapper
{
private Variable hairLength;
private Variable height;

public JsonEvidenceMapper(Network network)
{
if (network == null)
throw new ArgumentNullException("network");

this.hairLength = network.Variables[HairLengthName, true];
this.height = network.Variables[HeightName, true];
}

public void SetEvidence(IEvidence evidence, Person person)
{
evidence.SetState(this.hairLength.States[person.HairLength, true]);
evidence.Set(this.height, person.Height);
}

}

/// <summary>
/// Example that converts Json data to Bayes Server Evidence.
/// </summary>
private static void JsonStringExample(Network network)
{
var jsonPerson1 = "{\"HairLength\": \"Medium\",\"Height\": 110}";
var jsonPerson2 = "{\"HairLength\": \"Long\",\"Height\": 88}";

var hairLength = network.Variables[HairLengthName, true];
var height = network.Variables[HeightName, true];

var evidenceMapper = new JsonEvidenceMapper(network);
var evidence = new Evidence(network); // NOTE that if you performing inference, the Inference Engine has a property called Evidence which can be used instead

{
var person1 = JsonConvert.DeserializeObject<Person>(jsonPerson1);
evidenceMapper.SetEvidence(evidence, person1);

// Test the evidence ...
PrintPersonEvidence(evidence, hairLength, height);
}

{
var person2 = JsonConvert.DeserializeObject<Person>(jsonPerson2);
evidenceMapper.SetEvidence(evidence, person2);

// Test the evidence ...
PrintPersonEvidence(evidence, hairLength, height);
}

}

/// <summary>
/// Custom implementation of IEvidenceReaderCommand that is a factory for creating IEvidenceReader instances.
/// </summary>
private class JsonEvidenceReaderCommand<T> : IEvidenceReaderCommand
{
private Action<IEvidence, T> setEvidence;
private Func<Stream> newStream;

public JsonEvidenceReaderCommand(Action<IEvidence, T> setEvidence, Func<Stream> newStream)
{
if (setEvidence == null)
throw new ArgumentNullException("setEvidence");

if (newStream == null)
throw new ArgumentNullException("newStream");

this.setEvidence = setEvidence;
this.newStream = newStream;
}

public IEvidenceReader ExecuteReader()
{
return new JsonEvidenceReader<T>(this.setEvidence, this.newStream());
}
}

/// <summary>
/// A custom implementation of IEvidenceReader.
/// </summary>
private class JsonEvidenceReader<T> : IEvidenceReader
{
private Stream stream;
private IEnumerator<T> enumerator;
private Action<IEvidence, T> setEvidence;

private const int DisposedTrue = -1;
private const int DisposedFalse = 0;
private int disposed = DisposedFalse;

public JsonEvidenceReader(Action<IEvidence, T> setEvidence, Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");

if (setEvidence == null)
throw new ArgumentNullException("setEvidence");

this.stream = stream;
this.enumerator = EnumerateJson<T>(this.stream).GetEnumerator();
this.setEvidence = setEvidence;

}

public void Dispose()
{
Dispose(true);
}

private void Dispose(bool disposing)
{
if (Interlocked.Exchange(ref this.disposed, DisposedTrue) == DisposedFalse)
{
if(this.stream != null)
{
this.stream.Close();
this.stream = null;
}
}
}

~JsonEvidenceReader()
{
Dispose(false);
GC.SuppressFinalize(this);
}

public bool Read(IEvidence evidence, IReadOptions readOptions)
{
if (this.disposed == DisposedTrue)
{
throw new ObjectDisposedException(this.GetType().Name);
}

var result = this.enumerator.MoveNext();

if (result)
{
this.setEvidence(evidence, this.enumerator.Current);
}

return result;
}
}

private static IEnumerable<TResult> EnumerateJson<TResult>(Stream stream)
{
var serializer = new JsonSerializer();

using (var reader = new StreamReader(stream))
{
using (var jsonReader = new JsonTextReader(reader))
{
jsonReader.SupportMultipleContent = true;

while (jsonReader.Read())
{
yield return serializer.Deserialize<TResult>(jsonReader);
}
}
}
}

/// <summary>
/// Returns a stream of Json data.
/// </summary>
private static Stream GetJsonStream()
{
// We are using an in memory stream built from a string here to keep the example simple,
// but this could be returned from an HttpClient or from an ElasticSearch client etc...

var jsonString = "{\"HairLength\":\"Long\",\"Height\":100.0}{\"HairLength\":\"Medium\",\"Height\":120.0}{\"HairLength\":\"Medium\",\"Height\":99.0}";

return new MemoryStream(Encoding.UTF8.GetBytes(jsonString));

}

private static void JsonStreamExample(Network network)
{
var mapper = new JsonEvidenceMapper(network);

var evidenceReaderCommand = new JsonEvidenceReaderCommand<Person>(
(evidence, p) => mapper.SetEvidence(evidence, p),
() => GetJsonStream() // instead could use HttpClient or ElasticSearch client here for example
);

// the above evidenceReaderCommand can be passed to various Bayes Server API calls such as
// parameter learning

TestEvidenceReader(network, evidenceReaderCommand);

}

/// <summary>
/// Test our custom implementation if IEvidenceReaderCommand and IEvidenceReader are working.
/// </summary>
private static void TestEvidenceReader(Network network, IEvidenceReaderCommand evidenceReaderCommand)
{
var readOptions = new ReadOptions();

var hairLength = network.Variables[HairLengthName, true];
var height = network.Variables[HeightName, true];

var testEvidence = new Evidence(network);

using (var evidenceReader = evidenceReaderCommand.ExecuteReader())
{
while (evidenceReader.Read(testEvidence, readOptions))
{
PrintPersonEvidence(testEvidence, hairLength, height);
}
}
}

private static void PrintPersonEvidence(IEvidence evidence, Variable hairLength, Variable height)
{
Console.WriteLine("Person...");
Console.WriteLine("\t" + hairLength.States[evidence.GetState(hairLength).Value].Name);
Console.WriteLine("\t" + evidence.Get(height).Value);
}


}
}