File Coverage

blib/lib/Chorus/Engine.pm
Criterion Covered Total %
statement 17 58 29.3
branch 0 22 0.0
condition 0 26 0.0
subroutine 6 10 60.0
pod 3 4 75.0
total 26 120 21.6


line stmt bran cond sub pod time code
1             package Chorus::Engine;
2              
3 1     1   1251 use 5.006;
  1         3  
4 1     1   5 use strict;
  1         1  
  1         23  
5 1     1   5 use warnings;
  1         2  
  1         45  
6              
7             our $VERSION = '1.03';
8              
9 1     1   4 use Chorus::Frame;
  1         2  
  1         102  
10              
11 1     1   5 use constant DEBUG => 0;
  1         2  
  1         201  
12              
13             =head1 NAME
14              
15             Chorus::Engine - A very light inference engine combined with the frame model for knowledge representation.
16              
17             =head1 VERSION
18              
19             Version 1.04
20              
21             =cut
22              
23             =head1 INTRODUCTION
24              
25             Chorus-Engine makes possible to simply develop in Perl with an Articial Intelligence approach
26             by defining the knowledge with rules the inference engine will try to apply on your objects.
27            
28             Because inference engines use to waste a lot of time before finding interesting instanciations
29             for rules, the property _SCOPE is used to optimise the space on which each rule must be tested.
30            
31             This not necessary but, uou can combinate Chorus::Engine with Chorus::Frame which gives a first level
32             for knowledge representation. The inference engine can then work on Frames using the function 'fmatch'
33             top optimise the _SCOPE for the rules which work on frames.
34              
35             =cut
36              
37             =head1 SYNOPSIS
38              
39             use Chorus::Engine;
40              
41             my $agent = new Chorus::Engine();
42            
43             $agent->addrule(
44              
45             _SCOPE => { # These arrays will be combinated as parameters (HASH) when calling _APPLY
46             a => $subset, # static array_ref
47             b => sub { .. } # returns an array ref
48             },
49            
50             _APPLY => sub {
51             my %opts = @_; # provides $opt{a},$opt{b} (~ one combinaison of _SCOPE)
52              
53             if ( .. ) {
54             ..
55             return 1; # rule could be applied (~ something has changed)
56             }
57              
58             return undef; # rule didn't apply
59             }
60             );
61            
62             $agent->loop();
63              
64             =head1 SUBROUTINES/METHODS
65             =cut
66              
67             =head2 addrule()
68              
69             Defines a new rule for the Chorus::Engine object
70            
71             arguments :
72            
73             _SCOPE : a hashtable defining the variables and their search scope for instanciation
74             Values must be SCALAR or ARRAY_REF
75            
76             _APPLY : function which will be called in a loop with all the possible
77             combinaisons from scopes on a & b
78            
79             Ex. use Chorus::Engine;
80             use Chorus::Frames;
81            
82             my $e=Chorus::Engine->new();
83            
84             $e->addrule(
85            
86             _SCOPE => {
87              
88             foo => [ fmatch( .. ) ], # selection of Frames bases on the filter 'fmatch' (static)
89             bar => sub { [ fmatch( .. ) ] }, # same kind more dynamic
90             baz => [ .. ] # any other array (not only frames)
91              
92             },
93            
94             _APPLY => sub {
95             my %opts = @_; # provides $opt{foo},$opt{bar},$opt{baz}
96            
97             return undef if ( .. ); # rule didn't apply
98              
99             if ( .. ) {
100             .. # some actions
101             return 1; # rule could be applied
102             }
103            
104             return undef; # rule didn't apply (last instruction)
105             });
106             =cut
107            
108             =head2 loop()
109              
110             Tells the Chorus::Engine object to enter its inference loop.
111             The loop will end only after all rules fail (~ return false) in the same iteration
112            
113             Ex. my $agent = new Chorus::Engine();
114            
115             $agent->addrule( .. );
116             ..
117             $agent->addrule( .. );
118              
119             $agent->loop();
120             =cut
121              
122             =head2 cut()
123              
124             Go directly to the next rule (same loop, same agent). This will break all nested instanciation loops
125             on _SCOPE of the current rule. -> GO DIRECTLY TO NEXT RULE (SAME AGENT)
126            
127             Ex. $agent->addrule(
128             _SCOPE => { .. },
129             _APPLY => sub {
130             if ( .. ) {
131             $agent->cut(); # ~ exit the rule
132             }
133             );
134             =cut
135              
136             =head2 last()
137              
138             Breaks the current loop (on rules) for the current agent -> GO DIRECTLY TO NEXT AGENT
139             This will force a cut() too.
140            
141             Ex. $agent->addrule(
142             _SCOPE => { .. },
143             _APPLY => sub {
144             if ( .. ) {
145             $agent->last();
146             }
147             );
148             =cut
149              
150             =head2 replay()
151              
152             Restart FROM THE BEGINNING (1st rule) for the CURRENT AGENT. This will force a cut() too.
153            
154             Ex. $agent->addrule(
155             _SCOPE => { .. },
156             _APPLY => sub {
157             if ( .. ) {
158             $agent->replay();
159             }
160             );
161             =cut
162              
163             =head2 replay_all()
164              
165             Restart FROM THE BEGINNING for the FIRST AGENT. This will force a cut() too.
166            
167             Ex. $agent->addrule(
168             _SCOPE => { .. },
169             _APPLY => sub {
170             if ( .. ) {
171             $agent->replay_all();
172             }
173             );
174             =cut
175              
176             =head2 solved()
177              
178             Tells the Chorus::Engine to terminate immediately. This will force a last() too
179            
180             Ex. $agent->addrule(
181             _SCOPE => { .. },
182             _APPLY => sub {
183             if ( .. ) {
184             $agent->solved();
185             }
186             );
187             =cut
188              
189             =head2 reorder()
190              
191             the rules of the agent will be reordered according to the function given as argument (works like with sort()).
192             Note - The method last() will be automatically invoked.
193            
194             Exemple : the current rule in a syntax analyser has found the category 'CAT_C' for a word.
195             The next step whould invoque as soon as possible the rules declared as interested
196             in this category.
197            
198             sub sortA {
199             my ($r1, $r2) = @_;
200             return 1 if $r1->_INTEREST->CAT_C;
201             return -1 if $r2->_INTEREST->CAT_C;
202             return 0;
203             }
204              
205             $agent->addrule( # rule 1
206             _INTEREST => { # user slot
207             CAT_C => 'Y',
208             # ..
209             },
210             _SCOPE => { .. }
211             _APPLY => sub { .. }
212             );
213            
214             $agent->addrule( # rule n
215             _SCOPE => { .. }
216             _APPLY => sub {
217             # ..
218             if ( .. ) {
219             # ..
220             $agent->reorder(sortA); # will put rules interested in CAT_A to the head of the queue
221             }
222             }
223             );
224             =cut
225            
226             =head2 pause()
227              
228             Disable a Chorus::Engine object until call to wakeup(). In this mode, the method loop() has no effect.
229             This method can optimise the application by de-activating a Chorus::Engine object until it has
230             a good reason to work (ex. when a certain state is reached in the application ).
231             =cut
232            
233             =head2 wakeup()
234              
235             Enable a Chorus::Engine object -> will try again to apply its rules after next call to loop()
236             =cut
237              
238             =head2 reorderRules()
239              
240             use from rules body to optimize the engine defining best candidates (rules) for next loop (break the current loop)
241             =cut
242              
243             sub reorderRules {
244 0     0 1   my ($funcall) = shift;
245 0 0         return unless $funcall;
246 0           $SELF->{_RULES} = [ sort { &{$funcall}($a,$b) } @{$SELF->{_RULES}} ];
  0            
  0            
  0            
247 0           $SELF->replay;
248             }
249              
250             =head2 applyrules()
251              
252             main engine loop (iterates on $SELF->_RULES)
253             =cut
254              
255 1     1   5 use Data::Dumper;
  1         2  
  1         998  
256              
257             sub applyrules {
258              
259             sub apply_rec {
260 0     0 0   my ($rule, $stillworking) = @_;
261 0           my (%opt, $res);
262            
263             my %scope = map {
264 0           my $s = $rule->get("_SCOPE $_");
265 0 0 0       $_ => ref($s) eq 'ARRAY' ? $s : [$s || ()]
266 0           } grep { $_ ne '_KEY'} keys(%{$rule->{_SCOPE}});
  0            
  0            
267            
268 0           my $i = 0;
269            
270 0           my $head = 'JUMP: {' . join("\n", map { $i++; 'foreach my $k' . $i . ' (@{$scope{' . $_ . '}})' . " {\n\t" . '$opt{' . $_ . '}=$k' . $i . ";"
  0            
  0            
271             } keys(%scope)) . "\n";
272            
273             # TODO - SET variables from %opt HERE !!
274             # ..
275            
276 0           my $body = '$res = $rule->get(\'_APPLY\', %opt); last JUMP if $SELF->{_LAST} or $SELF->{_CUT} or $SELF->{_REPLAY} or $SELF->{_REPLAY_ALL} or $SELF->BOARD->SOLVED or $SELF->BOARD->FAILED';
277 0           my $tail = "\n}" x scalar(keys(%scope)) . '}';
278              
279 0           eval $head . $body . $tail;
280 0 0         if ($@) {
281 0           warn $@;
282 0           warn "DEBUG 1 - Rule '$rule->{_ID}' _SCOPE was : " . join(', ', keys(%scope)) . "\n";
283             }
284            
285 0 0 0       warn "DEBUG - Rule '$rule->{_ID}' returned TRUE. : " if $res and DEBUG;
286            
287 0   0       $stillworking ||= $res;
288              
289 0 0         delete $SELF->{_CUT} if $SELF->{_CUT}; # see eval (already processed) on prev line !!
290            
291 0 0 0       $SELF->{_QUEUE} = [] if $SELF->{_LAST} or $SELF->{_REPLAY} or $SELF->{_REPLAY_ALL} or $SELF->BOARD->SOLVED or $SELF->BOARD->FAILED;
      0        
      0        
      0        
292 0 0         delete $SELF->{_LAST} if $SELF->{_LAST};
293            
294 0 0 0       return undef if $SELF->{_REPLAY} or $SELF->{_REPLAY_ALL};
295              
296 0   0       $SELF->{_SUCCES} ||= $stillworking;
297            
298 0 0         return $stillworking unless $SELF->{_QUEUE}->[0];
299 0           return apply_rec (shift @{$SELF->{_QUEUE}}, $stillworking);
  0            
300             }
301              
302 0 0   0 1   return undef if $SELF->{_SLEEPING};
303 0 0         $SELF->{_QUEUE} = [ @{$SELF->{_RULES} || [] } ];
  0            
304 0           return apply_rec(shift @{$SELF->{_QUEUE}});
  0            
305             }
306              
307             my $AGENT = Chorus::Frame->new(
308              
309             cut => sub { $SELF->{_CUT} = 'Y' }, # returns true
310             last => sub { $SELF->{_LAST} = 'Y' }, # returns true
311             replay => sub { $SELF->{_REPLAY} = 'Y' }, # (returned value ignored)
312             replay_all => sub { $SELF->{_REPLAY_ALL} = 'Y' }, # (returned value ignored)
313            
314             loop => sub { $SELF->{_SUCCES} = 0; do {} while(applyrules() and (! $SELF->BOARD or ! $SELF->BOARD->SOLVED or ! $SELF->BOARD->FAILED)) },
315              
316             solved => sub { $SELF->BOARD->{SOLVED} = 'Y'; return undef },
317             failed => sub { $SELF->BOARD->{FAILED} = 'Y'; return undef },
318            
319             pause => sub { $SELF->{_SLEEPING} = 'Y' },
320             wakeup => sub { $SELF->delete('_SLEEPING')},
321            
322             addrule => sub { push @{$SELF->{_RULES}}, Chorus::Frame->new(@_) },
323             reorder => sub { reorderRules(@_) },
324            
325             debug => sub { $SELF->{_DEBUG} = shift }
326             );
327              
328             =head2 new
329             contructor : initialize a new engine
330             =cut
331              
332             sub new {
333 0     0 1   my $class = shift;
334 0           return Chorus::Frame->new(
335             @_,
336             _ISA => $AGENT,
337             _RULES => [],
338             )
339             }
340              
341             =head1 AUTHOR
342              
343             Christophe Ivorra, C<< >>
344              
345             =head1 BUGS
346              
347             Please report any bugs or feature requests to C, or through
348             the web interface at L. I will be notified, and then you'll
349             automatically be notified of progress on your bug as I make changes.
350              
351             =head1 SUPPORT
352              
353             You can find documentation for this module with the perldoc command.
354              
355             perldoc Chorus::Engine
356              
357              
358             You can also look for information at:
359              
360             =over 4
361              
362             =item * RT: CPAN's request tracker (report bugs here)
363              
364             L
365              
366             =item * AnnoCPAN: Annotated CPAN documentation
367              
368             L
369              
370             =item * CPAN Ratings
371              
372             L
373              
374             =item * Search CPAN
375              
376             L
377              
378             =back
379              
380              
381             =head1 ACKNOWLEDGEMENTS
382              
383              
384             =head1 LICENSE AND COPYRIGHT
385              
386             Copyright 2013 Christophe Ivorra.
387              
388             This program is free software; you can redistribute it and/or modify it
389             under the terms of either: the GNU General Public License as published
390             by the Free Software Foundation; or the Artistic License.
391              
392             See http://dev.perl.org/licenses/ for more information.
393              
394              
395             =cut
396              
397             1; # End of Chorus::Engine