Saturday, May 13, 2017

Expression evaluation on object based models


Let's look at how to mix object based models with logical expression evaluation.

Other articles in the AI Knowledge Based Reasoning series on this site:
Knowledge based reasoning in .net c#
Reasoning in Open or Closed models
Logic Expression evaluation with open-world assumption
Expression evaluation on object based models
Expression evaluation over time
Expression evaluation over time - Was?

Many systems work on an object model of some kind, to be able to integrate logical expression evaluation to an existing system you need some way to translate from that object model to a knowledge model. This is one way to do it, there are probably better ways out there. But this works for me : )

To be able to evaluate logical expressions on an object model, you first have to translate the object model into a Knowledge model. The idea here is to scope the amount of knowledge to a certain object (or objects), lets call that scope a Context, in the case of objects lets call it an ObjectContext.
public class Context
{
 public Guid Id { get; }
 public string Name { get; }
   
 public KnowledgeStore KnowledgeStore { get; }

 public Context(string name)
 {
  Id = Guid.NewGuid();
  Name = name;
  KnowledgeStore = new KnowledgeStore(this);
 }

 public EvaluationResult Evaluate(AExpression expression)
 {
  var facts = new Dictionary<ExpressionLeaf, EvaluationResult>();
  var leafNodes = expression.GetLeafNodes();
  foreach (var node in leafNodes)
  {
   var leaf = node.Leaf;
   if(leaf is KnowledgeNon)
    continue;
   var attr = leaf as KnowledgeAttribute;
   if (attr != null)
   {
    facts.Add(node, KnowledgeStore.Evaluate(attr));
   }
   var rel = leaf as KnowledgeRelation;
   if (rel != null)
   {
    facts.Add(node, KnowledgeStore.Evaluate(rel));
   }
  }
  if (facts.Values.Any(x => x == EvaluationResult.NotSure))
   return EvaluationResult.NotSure;
  return expression.TransformEvaluation(facts);
 }
}
So, the base Context object lets us Evaluate an Expression, as we assume an open world we also allow for the response NotSure.

And for the ObjectContext, we will use reflection to create the Knowledge from the objects that we throw at it.In my case, my model inherits from a BaseObject class, but it could be any object.
public class ObjectContext : Context
{
 public BaseObject Root { get; set; }
 public ObjectContext(BaseObject root) : base(root.ToString())
 {
  Root = root;
  KnowledgeStore.AddAttribute(new KnowledgeAttribute
  {
   Attribute = root.GetType().Name,
   Subject = Name
  }, Name);
  var fields = GetAllProperties(root);
  foreach (var field in fields)
  {
   KnowledgeStore.AddRelation(new KnowledgeRelation
   {
    Subject = field.Value,
    Relation = $"{field.Key}Of",
    Target = Name
   }, Name);
  }
 }

 private Dictionary<string, string> GetAllProperties(object obj)
 {
  var d = new Dictionary<string, string>();
  var properties = obj.GetType().GetProperties();
  foreach (var prop in properties)
  {
   var val = prop.GetValue(obj);
   if (val == null)
    val = string.Empty;
   d.Add(prop.Name, val.ToString());
  }
  return d;
 }
}

So basically we will take a snapshot of the model and allow for expression evaluation on that snapshot. If the underlying model changes, the context object would need to be recreated.
As you can see we add a suffix to each attribute name, so if the object has a field called Name, the knowledge attribute would be NameOf.

Lets look at some unit tests to see how this works.
[TestClass]
public class ObjectContextTest
{
 [TestMethod]
 public void ContextTest_Evaluate_Not_False()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionNot(new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() });
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Not_True()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionNot(new KnowledgeRelation { Relation = "NameOf", Subject = "T", Target = obj.ToString() });
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_And_BothTrue()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionAnd(
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() },
   new KnowledgeRelation { Relation = "NameOf", Subject = "Charlize", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_And_RightFalse()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionAnd(
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() },
   new KnowledgeRelation { Relation = "NameOf", Subject = "T", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_And_LeftFalse()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionAnd(
   new KnowledgeRelation { Relation = "NameOf", Subject = "T", Target = obj.ToString() },
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_And_BothFalse()
 {
  var obj = new Person("Charlize") { Gender = Person.GenderType.Female };
  var target = new ObjectContext(obj);
  var expr = new ExpressionAnd(
   new KnowledgeRelation { Relation = "NameOf", Subject = "T", Target = obj.ToString() },
   new KnowledgeRelation { Relation = "GenderOf", Subject = "male", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }
 [TestMethod]
 public void ContextTest_Evaluate_Or_BothTrue()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionOr(
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() },
   new KnowledgeRelation { Relation = "NameOf", Subject = "Kate", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Or_RightFalse()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionOr(
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() },
   new KnowledgeRelation { Relation = "NameOf", Subject = "Kate", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Or_LeftFalse()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionOr(
   new KnowledgeRelation { Relation = "NameOf", Subject = "Kate", Target = obj.ToString() },
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Or_BothFalse()
 {
  var obj = new Person("Charlize") { Gender = Person.GenderType.Female };
  var target = new ObjectContext(obj);
  var expr = new ExpressionOr(
   new KnowledgeRelation { Relation = "NameOf", Subject = "Kate", Target = obj.ToString() },
   new KnowledgeRelation { Relation = "GenderOf", Subject = "Male", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }


 [TestMethod]
 public void ContextTest_Evaluate_Xor_BothTrue()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionXor(
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() },
   new KnowledgeRelation { Relation = "NameOf", Subject = "Charlize", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Xor_RightFalse()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionXor(
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() },
   new KnowledgeRelation { Relation = "NameOf", Subject = "Kate", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Xor_LeftFalse()
 {
  var obj = new Person("Charlize");
  var target = new ObjectContext(obj);
  var expr = new ExpressionXor(
   new KnowledgeRelation { Relation = "NameOf", Subject = "Kate", Target = obj.ToString() },
   new KnowledgeAttribute { Attribute = "Person", Subject = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.True, actual);
 }

 [TestMethod]
 public void ContextTest_Evaluate_Xor_BothFalse()
 {
  var obj = new Person("Charlize") { Gender = Person.GenderType.Female };
  var target = new ObjectContext(obj);
  var expr = new ExpressionXor(
   new KnowledgeRelation { Relation = "NameOf", Subject = "T", Target = obj.ToString() },
   new KnowledgeRelation { Relation = "GenderOf", Subject = "female", Target = obj.ToString() }
   );
  var actual = target.Evaluate(expr);
  Assert.AreEqual(EvaluationResult.False, actual);
 }
}

So, just some thoughts on how to do this. This is far from completed and is still a work in progress used by one of my home projects. Any comments are appreciated.
If you wonder why this is not on my github repository, I feel that it doesn't really have that kind of quality yet. But with time it will get there.

All code provided as-is. This is copied from my own code-base, May need some additional programming to work.