File Coverage

blib/lib/Chorus/Engine.pm
Criterion Covered Total %
statement 12 47 25.5
branch 0 16 0.0
condition 0 9 0.0
subroutine 4 8 50.0
pod 3 4 75.0
total 19 84 22.6


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