Newsletter #2: December 2000/January 2001 Edition
Home Up Feedback Search

Products
Order
Downloads
Support
Articles & Tips
Recommended Links
About

 
 

 
 

RiverSoftAVG Newsletter #2
December 2000/January 2001
 
Hello and welcome again to the RiverSoftAVG New Millennium newsletter!  It's hard to believe the new year and the real new millennium is almost here.  We are gearing up to develop the next major release of the Inference Engine Component Suite, please take the time to fill out the questionnaire at the bottom of the newsletter.  Please also be aware that RiverSoftAVG will be on vacation the last week of December and the first week of January.  Happy Holidays and a Happy New Year!
 
Contents
 
 
Note: You received this newsletter because you are an owner of a RiverSoftAVG product.  If you received this newsletter by mistake or if for any reason you wish to not receive any future mailings from RiverSoftAVG, just reply to this message informing us so.  We apologize for any intrusion.
 
 
 
The Inference Engine Component Suite has been upgraded!  On December 9th, we released version 1.1 of the IECS.  The upgrade adds the following features:
 
bulletImproved CLIPS Compatibility
bulletBatch processing
bullet55 new functions including 36 CLIPS functions
This is a free upgrade so if you haven't gotten it yet, rush and get it at http://www.riversoftavg.com/patches.htm   The complete version history is available at http://www.riversoftavg.com/version_history.htm

 

All the RiverSoftAVG newsletters are now available online (the whole two of them :) ).  Hopefully, this will become a repository of useful information.  Go to http://www.RiverSoftAVG.com/articles_&_tips.htm

 

You may have noticed that RiverSoftAVG made the transition in late November to a new web host.  It was NOT a smooth transition for which we apologize profusely.  FrontPage is a quirky beast (gee, what a surprise!) and the only way to get the extensions to work properly was to modify pages online.  Everything is stable now and the web site should be better than ever.  The site is faster and we have added the capability to search the site.
 
 
The Inference Engine architecture provides rudimentary help for running your inference engines in threads besides your main one.  Perhaps you were not aware of it, but the TInferenceEngine component has Lock and Unlock methods.  You can use these methods to tell the inference engine to temporarily stop or block (while you change its properties or call other methods) and then continue, like so:
 
InferenceEngine1.Lock;
try
    // your code here
finally
    InferenceEngine1.Unlock;
end;
 
The lock and unlock methods use a critical section (TCriticalSection) to temporarily block another thread.  The inference engine's run method calls the lock and unlock every step to ensure you don't stomp on any inference engine structures (such as the Agenda) while it is inferring new facts.  Of course, this assumes that you call the Lock and Unlock methods as well.
 
For this example, we are going to make a simple appiclation that executes two TInferenceEngine components in auxiliary threads, leaving the main application thread free.  The source code and the expert systems are available from the web site at www.RiverSoftAVG.com/ThreadDemo.zip   The expert systems I use in this example are fullmab.ie and wordgame.ie.  These two expert systems are ideal because they take more than a couple of seconds to finish so we can see them executing.  Note that if you are compiling the thread demo from scratch and you only have the demo version of the Inference Engine Component Suite, the wordgame.ie will not work because it violates the demo limits.  You will receive Out Of Memory exceptions.  You can, however, modify the demo to load another expert system instead.
 
Setting Up The Main Form
 
First, let's set up the main application.  Create a new application and drop two TInferenceEngine threads on the form.  Drop two TListBox components on the form (to hold the output from the inference engines, you can use a TMemo component as well).  Finally, drop a TButton on the form, this button will be used to start or restart the threads.
 
To run the threads, create a OnClick event handler for the TButton.  In here, we will lock the inference engines, load our expert systems and resume the threads if they are stopped.  The code below does just that:
 
procedure TForm1.Button1Click(Sender: TObject);
begin
     ListBox1.Items.Clear;
     ListBox2.Items.Clear;
     // Lock Inference Engine1 and add the .IE file
     with InferenceEngine1 do
     begin
          // Lock the InferenceEngine so that we can modify it without stomping
          // on the other thread
          Lock;
          try
             // Tell the engine to stop when we wake it back up
             Halt := True;
             // Clear the rules, facts, and everything else
             Clear;
             LoadFromFile( 'fullmab.clp' );
             // reset the engine to prepare for inference
             Reset;
          finally
             Unlock;
          end;
     end;
     // Lock Inference Engine2 and add the .IE file
     with InferenceEngine2 do
     begin
          // Lock the InferenceEngine so that we can modify it without stomping
          // on the other thread
          Lock;
          try
             // Tell the engine to stop when we wake it back up
             Halt := True;
             // Clear the rules, facts, and everything else
             Clear;
             LoadFromFile( 'wordgame.clp' );
             // reset the engine to prepare for inference
             Reset;
          finally
             Unlock;
          end;
     end;
     if Thread1.Suspended then Thread1.Resume;
     if Thread2.Suspended then Thread2.Resume;
end;
 
To save memory, we are going to use another great feature of the Inference Engine Component Suite and share the user functions between the two TInferenceEngine components.  Shift-click the two components and empty out the Packages property set.  Now drop on the form the TUserPackages we might need, I dropped the TStandardPackage, TPredicatePackage, TStringPackage, TMathPackage, and TMiscPackage.  Do NOT set the Engine properties of the user packages, we will set that in the form OnCreate event.
 
A word of warning... sharing packages is a great feature but be careful of any user functions that store data in the object.  The two inference engines could stomp on each others' data.  For example, the TPrintOutFunction stores the last text output (to make it available as a prompt for the read functions).  I wanted to keep the example simple so the two expert systems we are using don't require user input.  This makes sharing every function safe in this case but you do want to be aware of it. 
 
Creating Our Thread object
 
So, to run an inference engine in another thread, we need to make a TThread descendant.  This thread will be responsible for executing the TInferenceEngine you supply it until it is terminated.  Create a new thread object using File->New... Thread Object.  I named the thread TIEThread.
 
For this thread, we want to pass to the constructor the TInferenceEngine component to run AND the TListBox to put the output.  First, we create two private fields of the object to hold the inference engine and the list box.  Here is our constructor so far:
 
constructor TIEThread.Create(IE: TInferenceEngine; ListBox: TListBox);
begin
     // Create thread suspended
     inherited Create( True );
     // IE is the inference engine to run
     FIE := IE;
end;
 
Of course, the Execute method is extremely simple:
 
procedure TIEThread.Execute;
begin
     while (not Terminated) do
           IE.Run;
end;
 
To send output to the list box, we need to create an OnPrintOut event handler in the thread and assign it to the inference engine:
 
procedure TIEThread.InferenceEnginePrintOut(Sender: TObject; OutID,
  Text: String);
begin
    ListBox.Items.Add( Text );
end;
 
Modify the constructor for the assign:
 
constructor TIEThread.Create(IE: TInferenceEngine; ListBox: TListBox);
begin
     // Create thread suspended
     inherited Create( True );
     // IE is the inference engine to run
     FIE := IE;
     IE.OnPrintOut := InferenceEnginePrintOut;
end;
 
BUT WAIT A SECOND!!!  We should not directly access VCL object methods and properties from this thread.  In fact, when we create the thread, Borland even includes a caution to wrap all access in Synchronize calls.  Ok, we can change the InferenceEngineOnPrintOut to call synchronized another thread method which updates the Listbox.  That would work, right?  Wrong.  Unfortunately, this simple fix will not work in the case.  To see why, look at our Button1OnClick event handler.  To protect the inference engine, the method locks it, changes it, and then unlocks it.  However, if the inference engine is already locked, the Lock method call will block the calling thread (in this case, the main thread).  If you remember, I also told you the Run method calls the Lock and Unlock method whe executing a step.  If the inference engine has locked itself to execute a step and that step calls the PrintOut method, the TIEThread object will try to synchronize (blocking itself) with the main thread.  Deadlock!  Uh oh.
 
So now what do we do?  The main problem is the printout call.  If we were not trying to synchronize with the main thread, no problem.  If we put on our thinking caps, we can come up with a solution (probably more than one).  The solution I came up with is to add a message passing thread between the TIEThread and the main thread.  The TIEThread object will never actually interact with the main thread.  Instead, it will push a message onto the communications thread's queue and immediately return.  The communications thread will periodically check its queue, and, when it finds a message, pop it off, synchronize with the main thread, and modify the listbox.  The secret is that the message queue will use one critical section and the Listbox modification will use the main thread's synchronization.  The communication thread will release the pop the queue and release the critical section (allowing the TIEThread object to grab the critical section and push messages onto the queue) BEFORE synchronizing with the main thread.
 
In the interests of brevity (this tip is already WAY too long), I won't list the TIECommThread source.  Please get the source code from the web site.  To summarize, the TIECommThread will allocate a TStringList (as our queue), a critical section (to protect access to the queue), and methods to push and pop.  The TIEThread object is changed to create and start a TIECommThread (as well as terminate it), and the OnPrintOut method calls the TIECommThread.Push method.
 
constructor TIEThread.Create(IE: TInferenceEngine; ListBox: TListBox);
begin
     // Create thread suspended
     inherited Create( True );
     Priority := tpLower;
     // create the communications thread
     FCommThread := TIECommThread.Create( ListBox );
     FCommThread.FreeOnTerminate := True;
     // IE is the inference engine to run
     FIE := IE;
     IE.OnPrintOut := InferenceEnginePrintOut;
end;
 
destructor TIEThread.Destroy;
begin
     CommThread.Terminate;
     inherited;
end;
 
procedure TIEThread.InferenceEnginePrintOut(Sender: TObject; OutID,
  Text: String);
begin
     // pass the message to the communication thread
     CommThread.Push( Text );
end;
 
Wrapping up
 
Ok, what is next?  Way back at the beginning of the tip, I promised you we would add the TUserPackages to the inference engines on form construction.  That is exactly what I am doing here.  We also need to allocate the two TInferenceEngine threads.  Declare two fields, Thread1 and Thread2, of type TIEThread.  We will create them in the constructor:
 
procedure TForm1.FormCreate(Sender: TObject);
begin
     // Add the packages we need to both inference engines,
     // sharing resources and saving memory.  Note, in a real application
     // you would NOT want TPrintOutFunction and TReadXXXFunction to share
     // since they access one variable.  We are only going to run non-input
     // expert systems so we will be ok
     StandardPackage1.Reasoners.Add( InferenceEngine1 );
     StandardPackage1.Reasoners.Add( InferenceEngine2 );
     MathPackage1.Reasoners.Add( InferenceEngine1 );
     MathPackage1.Reasoners.Add( InferenceEngine2 );
     PredicatePackage1.Reasoners.Add( InferenceEngine1 );
     PredicatePackage1.Reasoners.Add( InferenceEngine2 );
     StringPackage1.Reasoners.Add( InferenceEngine1 );
     StringPackage1.Reasoners.Add( InferenceEngine2 );
     MiscPackage1.Reasoners.Add( InferenceEngine1 );
     MiscPackage1.Reasoners.Add( InferenceEngine2 );
     // Create a thread for each inference engine
     Thread1 := TIEThread.Create( InferenceEngine1, ListBox1 );
     Thread2 := TIEThread.Create( InferenceEngine2, ListBox2 );
end;
 
Finally, we need terminate the threads.  We will put the terminate code in the OnDestroy event:
 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     Thread1.Terminate;
     Thread2.Terminate;
     // wait for a couple seconds so that the threads can terminate,
     // with a real application, you would probably want to wait on 2 variables
     // that would be set in the thread's OnTerminate event
     Sleep(2000);
end;
 
Conclusion
 
Making the inference engine component suite work with multiple threads is certainly possible.  However, before starting the work of doing so, perhaps you should ask yourself whether you need to.  Making a multi-threaded program can be a big headache, especially to debug.  Instead, consider using the TInferenceEngine.Run( 1 ) command.  This method allows you to tell the inference engine to execute one step only before returning.  Besides allowing you to avoid threading, you also avoid thread balancing issues - starving some threads at the expense of others.  You can call the Run(1) method for multiple inference engines and ensure each gets an equal amount of CPU power.
 
Please keep in mind that this article is meant as a starting point for developing your own applications using the IECE in separate threads.  The example has been simplified to make it as short as it can be.  The threads do NOT handle exceptions, which would definitely cause problems if they occured (the two IE files in this example don't though).  You also would want to consider using only one communication thread to avoid bogging the system down with too many threads.
 
I hope this article/tip has been helpful.  Any questions or comments, please email me at tggrubbNO@SPAMRiverSoftAVG.com

 

We are starting the work on the next upgrade for the IECS.  We need to know how we are doing and what you would like to see.  Please take the time to fill out the survey below.

Inference Engine Component Suite Survey

December, 2000

 

1)     How much time have you spent working with the Inference Engine Component Suite?

a)      More than an hour a week

b)      Hour a week

c)      Less than an hour a week

d)      Haven’t installed it

 

2)     What is your favorite feature of the Inference Engine Component Suite?

____________________________________________________________________________________

 

3)     What is your least favorite feature of the Inference Engine Component Suite?

____________________________________________________________________________________

 

4)     What feature is missing from the suite that you would most like to see?

a)      Database Support

b)      Better editors

c)      Better demos

d)      Component Setup wizard

e)    Fuzzy Logic

f)      Other, specify __________________________

 

5)     What is the best (most useful, most inspiring) demo included with the suite?

a)      Advanced Console Demo

b)      Basic Console Demo

c)      Paint Scripting Demo

d)      Personnel Evaluation Demo

 

6)     What kind of demo would you like to see that is not included?

____________________________________________________________________________________

 

7)     Would you buy, or recommend to buy, the Inference Engine Component Suite?

a)      Yes

b)      No

c)      Maybe

 

8)     Other comments

____________________________________________________________________________________

____________________________________________________________________________________

____________________________________________________________________________________

____________________________________________________________________________________

 

 

Please rate the following items:

Excellent

Good

Average

Poor

1)     What is your overall impression of the Inference Engine Component Suite?

 

 

 

 

2)     How well did the suite meet your expectations? (i.e., were you disappointed?  Writing expert systems is too hard, etc.)

3)     How easy was the installation process?

 

 

 

 

4)     How easy is it to add expert systems to your applications with the suite?

 

 

 

 

5)     How would you rate the ability of the suite to script your applications?

 

 

 

 

6)     How easy is it to make expert systems at all?

 

 

 

 

7)     How would you rate the design of the suite?

 

 

 

 

8)     How is the execution speed of the inference engine?

 

 

 

 

9)     How is the stability of the suite?

 

 

 

 

10)  How are the help files?

 

 

 

 

11)  How would you rate the support you received?

 

 

 

 

12)  How helpful are the demos?

 

 

 

 

13)  How is the quality of the demos?

 

 

 

 

14)  How are the editors (Rule editor, Fact editor, etc)?

 

 

 

 

15)  How is the memory footprint of the suite?

 

 

 

 

 

 
Send mail to webmasterNO@SPAMRiverSoftAVG.com with questions or comments about this web site.
Copyright © 2002-2010 RiverSoftAVG
Last modified: September 20, 2010