microsoft / Guan

Guan is a cross-platform, general-purpose logic programming library with a C# API for external predicate implementation. It is a close approximation of Prolog, with extended capabilities and some differences.
MIT License
72 stars 5 forks source link

Getting started with Guan? #14

Closed d-vyd closed 2 years ago

d-vyd commented 2 years ago

Hi! Is Guan an ongoing project? I'm fairly new to c# and Visual Studio but would really like to experiment with it. I don't see the package available via Nuget. Could you explain the steps necessary to include Guan in a simple application? From the p1.test file, it looks like an application might submit facts to the Guan subsystem via the keyword "rules" and ask questions via the keyword "query" with content in Prolog syntax as a string?

GitTorre commented 2 years ago

Hi,

Yes, Guan is an ongoing project, developed in the open, and currently in Preview. It will become 1.0 soon.

You can see a working example of consuming Guan and using Guan's API here: https://github.com/microsoft/service-fabric-healer This project also explains, in detail, how it employs Guan (see the related documentation files).

Guan, like Prolog, processes logic rules, which are typically housed in text files (but can be string variables, as well, in the consuming program).

Guan is available in the public nuget.org gallery: https://www.nuget.org/packages/Microsoft.Logic.Guan/1.0.1-Preview You can include the library in multiple ways in the consuming program (CLI, package manager, PackageReference, etc).

You can learn more about logic programming here (intro, concepts): https://www.metalevel.at/prolog/concepts Since Guan is a close approximation of Prolog, the basic concepts and much of the syntax is very similar.

Also, please read the first 5-7 chapters of this free online book: http://learnprolognow.com/ It will prove very helpful before you start writing logic rules.

Your idea for a Getting Started document on this repo is a good one. I will create a Work Item for it. Thanks.

d-vyd commented 2 years ago

Thank you so much for this detailed response. I'll look at the resources you linked to.

d-vyd commented 2 years ago

Hi Charles,

I combed through the FabricHealer solution and found RepairTaskManager.cs which uses Guan. The code is beyond my programming abilities but is well commented, so I'll try to figure it out. I see that the Prolog terms are stored in text files in the LogicRules directory.

As an alternative, I have CSProlog working because of the short example at https://github.com/jsakamoto/CSharpProlog and discussion in the related forum. It actually runs on my phone via Xamarin.Forms. In the long run, I'd like to switch to a more current Prolog implementation like Guan.

-david

GitTorre commented 2 years ago

The key things to look at in FabricHealer (which is a narrow, domain-specific implementation with respect to Guan, but Guan is a key ingredient) are the logic rules themselves, the external predicate implementations (see the Repair\Guan folder), and also how the facts (data from a different service, FabricObserver) are presented to Guan for use in related logic rules, how the external predicates are initialized and made available to the Guan query engine, and how to execute a query. The function RunGuanQueryAsync in RepairTaskManager.cs is where you should begin. For sure there is a lot going on in FabricHealer that is unrelated to Guan, but the code is organized in such a way that it should be relatively easy to focus on how Guan is employed without also understanding the unrelated code.

Basically, in FabricHealer, logic (with Guan) forms the basis of repair (auto-mitigation) workflow definition/configuration. Let's look at a simple logic rule that would be in AppRules.config.txt (the repair target in this case is a Service Fabric application):

Mitigate(AppName="fabric:/PortEater42", MetricName="EphemeralPorts", MetricValue=?MetricValue) :- ?MetricValue >= 8500,
    TimeScopedRestartCodePackage(4, 01:00:00).

The rule above is composed of a head (or goal), Mitigate, which is full of facts (or data from FabricObserver related to the application-level issue that was detected). As you can see in RunGuanQueryAsync, this information is stored in a CompoundTerm object named "Mitigate" (which, of course, is also the name of the goal or head in all rules) as named Arguments with corresponding values (of varying types). These arguments are then used in the sub rules (or sub goals) and form the basis of the logical workflow. You can interpret the rule to mean: If an application named "fabric:/PortEater42" is consuming 8500 or more ports in the dynamic range (aka ephemeral ports) then restart the application's code package (this is a Service Fabric concept and it will eventually safely restart the offending service process).

There are some interesting things here:

Mitigate(AppName=?AppName, MetricName="EphemeralPorts", MetricValue=?MetricValue) :- ?AppName == "fabric:/PortEater42", ?MetricValue >= 8500,
    TimeScopedRestartCodePackage(4, 01:00:00).

However, that adds unnecessary noise or complexity to the sub rules; you already have a head full of facts, so you can just use a more convenient (easier to read, less logic) way of expressing if this is the application we're looking for (AppName="fabric:/PortEater42") and that the MetricName is "EphemeralPorts". So, testing for named arguments in the goal or head is a straightforward and easy to read (and write) approach for rule composition.

Sub rules here are really boolean paths. That is, each sub rule will evaluate to either be true or false (very basic logic!). If true, the next sub rule is evaluated until we reach an external or internal predicate (the auto-mitigation to execute, which is done by FabricHealer, not Guan). Thus the notion of path or workflow that leads to some mitigation or not (the rule either succeeds or fails at some point in the sub rule logic, including the final mitigation step in this case - if that fails, then the next rule will run).

TimeScopedRestartCodePackage() :- RestartCodePackage(DoHealthChecks=true, MaxWaitTimeForHealthStateOk=00:10:00).

This will run an external predicate called RestartCodePackage (implemented in C#, just like Guan is) that will fire off a repair job (managed by FH and yet another service, RepairManager). The rule will succeed or fail based on the boolean result returned by the executor (FabricHealer) and surfaced to Guan (and thus the rule) by the external predicate.

Spend some time looking at the details of RestartCodePackagePredicateType located in Repair\Guan folder. You will notice a very similar pattern in most of the external predicates, though some are not boolean in nature (so, they do not derive from or implement Guan's BooleanPredicateResolver type). In some cases, the external predicates return (or, more formally, bind a value to some named argument) that is then used in sub rules (these derive from Guan's GroundPredicateResolver). An example of this type of external predicate is GetHealthEventHistoryPredicateType.

You should also read through the documentation on the FabricHealer repo: https://github.com/microsoft/service-fabric-healer/blob/main/Documentation/LogicWorkflows.md and not only focus on the implementation details until you are comfortable with the basic concepts.

Have fun and learn!

d-vyd commented 2 years ago

Thank you for targeting my attention and presenting a walk-though of the code. I will study RunGuanQueryAsync. But, could I quickly run my use case by you to make sure I'm not heading toward a dead end or a mine field? I intend to use Guan to hold game state (reality) and characters' beliefs (about reality) in a turn and text-based mobile app. In a previous desktop prototype, I used a planner (fastdownward) for knowledge representation and action planning, constantly translating between simulation/game objects (C#) and PDDL. I'm hoping that Guan will let me (1) unify these parts more so I don't have to keep translating and (2) update state and beliefs in memory via assert and retract so I don't have to reload all data every turn for each character. From my reading so far about Prolog, this seems feasible. I'm looking at Ivan Bratko's PROLOG Programming for Artificial Intelligence and Brachman & Levesque's Knowledge Representation and Reasoning.

EDIT (01/11/2022): I've started looking through the code you suggested and will likely have some questions in a few days.

d-vyd commented 2 years ago

Below, I report on my progress. Also, I see that Guan has Prolog’s assert in-built predicate, but I don’t see retract in the documentation. Has it been implemented? For example, can I add and remove clauses from the variable module created here Module module = Module.Parse("external", repairRules, functorTable);? Based on my reading, a Prolog with both these predicates is far more powerful than a Prolog with only one.

You suggested I study the following concepts:

1. the logic rules themselves The rules appear to follow traditional Prolog syntax except CompoundTerms have named arguments. I don't remember seeing that in Prolog, but it’s a welcome addition.

2. the external predicate implementations I understand that external predicates grant Guan access to objects coded in c#, but their implementation may be beyond my programming skills right now. In particular, I'm not familiar with c#'s async functionality and I don't understand the purpose of Singleton here: public static GetHealthEventHistoryPredicateType Singleton(string name, RepairTaskManager repairTaskManager, TelemetryData foHealthData). The example in the Readme is easier to follow.

3. how the facts (data from a different service, FabricObserver) are presented to Guan for use in related logic rules I’m still trying to understand this step.

4. how the external predicates are initialized and made available to the Guan query engine This is straight forward, right? functorTable.Add(CheckFolderSizePredicateType.Singleton(RepairConstants.CheckFolderSize, this, foHealthData));

5. how to execute a query I see where this occurs: return await queryDispatcher.RunQueryAsync(compoundTerms).ConfigureAwait(false); but I don't understand what happens when Guan decides which goal can be realized (made true). I expected Mitigate to trigger an external predicate of the same name that takes the bound variables as arguments. But, I didn’t find such a predicate. For example, in my application, if a Prolog rule determined whether to enable a GUI component (make it “clickable”), it would be convenient for the rule head (goal) to do this directly.

GitTorre commented 2 years ago

Hi,

Guan does not have all of the built-in predicates that Prolog has today.

It is important to understand that Guan is not a complete implementation of Prolog in C# (nor is that the goal). Guan is a close approximation, as stated in the main Readme, with some differences and some extended capabilities.

retract is not implemented today (there are probably many others, as well), but we can add it.

Singleton (single static instance) usage for FabricHealer predicates is a FabricHealer design decision, not a Guan requirement.

External predicates in Guan are no different than external predicates in Prolog, conceptually. The idea is that an external predicate has an external (to the logic rule from where it is used, to Guan itself) implementation that can bind a value to some variable for use in a rule, for example (for use by Guan). The internal implementation of an external predicate can do whatever it wants (and, in Guan's case, be written in C#), but it must be a PredicateType, implement the required Guan functions for some supported Guan resolver type (BooleanPredicateResolver, GroundPredicateResolver, etc..). This is made clear in the FabricHealer implementations.

With Guan, you can readily write external predicates in the same language that Guan itself is written in: C#. You have the entire .NET Standard Base Class Library at your disposal and can interoperate with any .NET Standard library or implementation (like the FabricHealer code base, which is decoupled and unrelated to Guan; or some .NET package you install from nuget.org).

Mitigate is the named CompoundTerm that is the head or goal of all FabricHealer rules. The facts from FabricObserver are housed (values are bound to named objects) in named arguments of the CompoundTerm Mitigate that are used in sub rules (made variables, for example, with the use of ? symbol): Mitigate(MetricName=?MetricName) :- ?MetricName == "Foo", ... or Mitigate(MetricName="EphemeralPorts", MetricValue=?MetricValue) -: ?MetricValue >= 5000,...

I am not sure what you mean in terms of rule heads or goals like Mitigate directly doing things.

The notion of Guan doing things related to the consuming program is precisely why you would write an external predicate. Guan only parses and executes logic rules (and of course has built-in predicates that afford very useful capabilities within these rules without having to write external predicate implementations). It has no notion of what the external (consuming) program does. This is, again, where external predicates come into play.

There is a work item to create a Getting Started document that will be decoupled from an existing program (like FabricHealer) that employs Guan to limit confusion. It will be written as soon as possible.

...Charles

d-vyd commented 2 years ago

Charles, thank you for reminding me that Guan is not a complete Prolog implementation. I'll continue using CSharpProlog for the time being and check back here to read the getting started document.

d-vyd commented 2 years ago

I'm still lurking. Since I was last here, I learned that Mercury (another Prolog-like language) can compile to .NET assemblies and be included in C# projects. So, that is another option for me. May I ask, by "as soon as possible" for the Guan getting started document, do you mean within the next two weeks, two months? I see you also added the Retract statement as a work item. Is there a timeline for that?

GitTorre commented 2 years ago

Hopefully, the getting started doc will ship in the next week or sooner. It will be based on the test project, with explanations of what is going on there, and some simple samples that are unrelated to FabricHealer or some larger consuming program.. For retract, there is no timeline yet. That will be done by Lu. I will touch base with him then let you know here about a timeline. Thanks for checking in.

d-vyd commented 2 years ago

Thanks for the update. Does Guan use the standard Prolog approach of Selective Linear Definite (SLD) clause resolution and depth first search with backtracking? I do see that it offers a novel "forward cut", but is that the only resolution difference?

GitTorre commented 2 years ago

We use standard SLD resolution in Guan.

GitTorre commented 2 years ago

Getting Started section shipped. Also, a GuanExamples project that can be used to experiment with Guan inside a host application. The Getting Started document will likely evolve, but the assumption is that users of Guan already understand logic programming. The goal is to get folks up to speed with how to use Guan in a containing .NET application. GuanExamples is a .NET Core 3.1 console application. The sample query expressions, logic rules, and external predicates are very simplistic, by design.

Since it is a sample app, the idea is that devs will experiment with crafting rules and executing queries, writing External Predicates, etc...

Feel free to add to the document if you want to showcase things you've learned that others will benefit from. Thanks in advance for experimenting with Guan and providing feedback.

...Charles