File Coverage

blib/lib/AI/FuzzyLogic.pm
Criterion Covered Total %
statement 34 319 10.6
branch 0 116 0.0
condition 0 18 0.0
subroutine 12 94 12.7
pod 7 42 16.6
total 53 589 9.0


line stmt bran cond sub pod time code
1              
2             package AI::FuzzyLogic;
3              
4             # hi! reading through the code? i try to get the infrastructure stuff out of the
5             # way first, so it is boring for a while. search for the string "conversion"
6             # below to find the start of the meat. "operators" follows quickly after.
7             # unary operators come first, then binary. this is where the guts of this implementation
8             # of fuzzy logic are. if you're looking for infrastructure, accessors and constructurs
9             # and such come first, and for anything more complex, i suggest you read the ntoes
10             # at the end of the file. i've tried to comment and document well, but please
11             # let me know if something could be improved. good luck!
12              
13             =head1 NAME
14              
15             AI::FuzzyLogic - Fuzzy Set Operations and Tools
16              
17             =head1 SYNOPSIS
18              
19             use AI::FuzzyLogic;
20              
21             $i = new AI::FuzzyLogic $unittype, @numbers; # new set with one subset
22             $i = new AI::FuzzyLogic 'age', 0, 0.1, 0.2, 0.1, 0; # same thing
23              
24             $i = new AI::FuzzyLogic $subset1, $subset2, $subset3; # new set with several subsets
25              
26             # another syntax for building a set with several subsets:
27             $i = AI::FuzzyLogic->new(
28             AI::FuzzyLogic->new('distance', 0.0, 0.1, 0.1, 0.5, 0.8, 0.6, 0.3, 0.0),
29             AI::FuzzyLogic->new('time', 0.3, 0.3, 0.1, 0.1, 0.1, 0.2, 0.3, 0.3),
30             AI::FuzzyLogic->new('heat', 0.0, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3, 0.2),
31             );
32              
33             # constructors for explicit combinational behavior:
34             $i = new AI::FuzzyLogic::Correlator 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
35             $i = new AI::FuzzyLogic::Permutator 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
36             $i = new AI::FuzzyLogic::Discriminator 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
37             $i = new AI::FuzzyLogic::Abstractor 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
38              
39             # change combinational behavior:
40             $set->as_correlator(); # operations work on matching subsets of same type
41             $set->as_permutator(); # operations work across all subsets of each set
42             $set->as_discriminator(); # operations best matching subset from right for each on left
43             $set->as_abstractor(); # operations return one set with one subset summerizing fit
44              
45             $i->add_subsets($j); # combine subsets or other sets in
46              
47             abs($i) # defuzzify to integer (centroid - curve middle, x axis)
48             0+$i # defuzzify to integer (mean - average curve height, y axis)
49              
50             $a & $b # intersection of sets
51             $a | $b # union of sets
52             $i++ # normalize curve to 1.0
53             $i-- # stretch curve to edges
54             ~$i # negate set
55             $i ** 0.5 # dialation
56             "$i" # convert subsets to ASCII graphs
57              
58             $a + $b # sum sets
59             $a - $b # subtract sets
60             $a * $b # multiply sets - useful for sensitivity control
61             $a / $b # divide sets - useful for sensitivity control
62              
63             $h->larger($a) # boolean: does $h completely encompass $a?
64              
65             $a ^ $b # xor: same as ~($a | $b)
66             $a < $b # compare volume: is $a smaller?
67             $a > $b # compare volume: is $a larger?
68              
69             @sets = $a->unwrap(); # get subsets as list of AI::FuzzyLogic::Subset objects
70             @sets = $a->query_type('type'); # get subsets of type 'type' as a list of AI::FuzzyLogic::Subset objects
71             $a->change_type('fromtype', 'to'); # change type of subsets of type 'fromtype' to 'to'
72              
73             =head1 DESCRIPTION
74              
75             Performs all basic operations on Fuzzy Sets. Use English-like, intentionally
76             vague objects representing concepts with which to make inferences. The inferences might be approximate
77             reasoning about precise knowledge, or precise reasoning about approximate knowledge. This
78             vagueness allows the capture and application of human expert knowledge.
79              
80             Overloads Perl operators to perform operations on Fuzzy Sets.
81              
82             =head2 Other Fuzzy Modules
83              
84             L and L appear to better supported and more mature modules.
85              
86             =head2 How AI::FuzzyLogic is Different
87              
88             It doesn't attempt to provide a structure for building
89             inference chains - that is left to regular Perl code using overloaded operators, C
90             statements, and the like.
91             So, a major feature is the operator overloading and that interface.
92              
93             We also define a larg set of operations, introduce sets-of-sets
94             and combinational, permutational behavior for working on them.
95              
96             Extensible framework. Modules in this distribution may be subclassed to define new
97             Fuzzy operations, combinational behaviors, and other features. Extensions may be
98             added to this distribute (if I like them), or you may distribute them seperately,
99             with this module as a dependency.
100              
101             =head2 Terminology
102              
103             This is not standard Fuzzy Logic terminology but instead lingo used in this module.
104             Sets (AI::FuzzyLogic) contain subsets (AI::FuzzyLogic::Subset). Subsets contains elements.
105             Elements are just numbers in an array (this implementation may change, but it is useful
106             to think of it this way).
107             Elements are also called segments sometimes, as in segments in a LED display.
108             Combinational Behavior controls what happens when an operation is performed
109             between two sets, one or both of which have more than one subset.
110              
111             =head2 Methods
112              
113             These public methods expose parts of the interface that aren't available through the
114             overloaded syntax. The overloaded syntax is of course the ability to use Perl built
115             in operators on Fuzzy sets and have Fuzzy operations performed.
116              
117             =cut
118              
119             our $VERSION='0.06';
120              
121 1     1   6887 use strict;
  1         2  
  1         41  
122 1     1   7 use warnings;
  1         1  
  1         35  
123              
124 1     1   957 use English::Reference;
  1         377  
  1         73  
125 1     1   6 use Scalar::Util 'blessed';
  1         2  
  1         126  
126              
127 1     1   582 use AI::FuzzyLogic::Subset;
  1         2  
  1         37  
128              
129             #
130             # terminology
131             #
132              
133             # *_inner routines are logic defined as part of the subset. for example, to compare the
134             # mean of the curves of two subsets, each subset would be asked for its mean, then those
135             # numbers would be compared.
136              
137             #
138             # debugging
139             #
140              
141 1     1   981 use lib '/home/projects/transient';
  1         800  
  1         6  
142             #use Nark;
143             #Nark::nark sub { print shift()."\n" };
144              
145             $SIG{__DIE__} = $SIG{INT} = sub {
146             # help us locate endless loops.... testing.
147             # when someone does kill -INT from the command line, dump our stack and exit
148             print STDERR shift, map { (caller($_))[0] ? sprintf("%s at line %d\n", (caller($_))[1,2]) : ''; } 0..30;
149             print STDERR join "\n", @_;
150             exit 1;
151             };
152              
153             #
154             # overload
155             #
156              
157             # if $_[2] is true, it means that the arguments order was swapped so that
158             # the object reference would come first. this way, the first two args
159             # to us are the args to operation that was overloaded, except that the
160             # overloaded object always comes first in cases where an operation is being
161             # performed against both an overloaded object and a regular number.
162              
163             sub objify {
164              
165             # if $_[2], then reverse the args.
166             # if one of the args isn't an object, create it as a new us.
167             # new objects created from numbers are sets with a single subset with that number repeated
168             # across three elements.
169             # this wraps our internal overloaded methods. our return value is fed directly to them.
170              
171 0 0   0 0 0 @_ = ($_[1], $_[0]) if $_[2];
172              
173 0 0       0 @_ = ($_[0], (__PACKAGE__->new('unknown', ($_[1]) x 3))) if ! ref $_[1];
174              
175 0         0 return @_;
176              
177             }
178              
179             use overload
180 0     0   0 '&' => sub { band(objify(@_)); }, # set intersection (min)
181 0     0   0 '|' => sub { bior(objify(@_)); }, # set union (max)
182 0     0   0 '^' => sub { bxor(objify(@_)); }, # set xor ;)
183 0 0 0 0   0 '+' => sub { $_[2] && !$_[1] ? numify2($_[0]) : badd(objify(@_)); },
184             # set summation, or if 0+, defuzzification
185 0     0   0 '0+' => sub { numify2(@_) }, # defuzzify - mean
186 0     0   0 'abs' => sub { numify(@_) }, # defuzzify - centroid
187 0     0   0 '-' => sub { bsub(objify(@_)); }, # set difference
188 0     0   0 '*' => sub { bmul(objify(@_)); }, # set multiply
189 0     0   0 '/' => sub { bdiv(objify(@_)); }, # set divide
190 0     0   0 'neg' => sub { bneg(objify(@_)); }, # opposite - invert set
191 0     0   0 '~' => sub { bneg(objify(@_)); }, # opposite - invert set
192 0     0   0 '**' => sub { bpow(@_); }, # dialation (2nd arg must be number)
193 0     0   0 '<' => sub { bles(objify(@_)); }, # which has less area?
194 0     0   0 '>' => sub { bgre(objify(@_)); }, # which has more area?
195 0     0   0 '""' => sub { stringify(@_) }, # make pretty little charts
196 0     0   0 '%' => sub { bmod(objify(@_)); }, # undef - how many times does one set fit in another?
197 0     0   0 '<<' => sub { brsh(objify(@_)); }, # undef
198 0     0   0 '>>' => sub { blsh(objify(@_)); }, # undef
199 0     0   0 '<=>' => sub { bcmp(objify(@_)); }, # compare volume
200 0     0   0 'cmp' => sub { bstrcmp(objify(@_)); }, # compare center-of-mass
201 1     1   2966 'bool' => sub { numify2(@_); }; # is this set "true"?
  1     0   1148  
  1         33  
  0         0  
202              
203             # XXX is this todo or what? suggestions for operations?
204             # min: turn a set into a singleton based on highest point or center of mass or something. defuz.
205             # max: normalize the set to exist from 0.0 to 1.0. regenerate sets that got beat down.
206              
207             #
208             # constructors
209             #
210              
211             =head3 new
212              
213             C comes in two basic forms.
214              
215             Create a new set, with exactly one subset, from raw input data:
216              
217             $i = new FuzzyLogic $unittype, @numbers; # new set with one subset
218             $i = new FuzzyLogic 'age', 0, 0.1, 0.2, 0.1, 0; # same thing
219              
220             Create a new set, with potentially many subsets, from several existing subsets.
221              
222             $i = new FuzzyLogic $subset1, $subset2, $subset3; # new set with several subsets
223              
224             Subsets can be obtained form existing sets using the C method:
225              
226             $i = new FuzzyLogic $set1->unwrap(), $set2->unwrap();
227              
228             C may return any number of subsets.
229              
230             Though the module will extract the subsets from sets should sets be passed to
231             the constructor. This is like perl arrays - combining arrays flattens them
232             all into one large one. No, there is no equivilent to references. See the
233             L for another note on this.
234              
235             AI::FuzzyLogic::Abstractor is the default type of new objects. If something else
236             is desired, it should be specified explicitly, as the default is likely to change
237             in future versions.
238              
239             # constructors for explicit combinational behavior:
240             $i = new AI::FuzzyLogic::Correlator 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
241             $i = new AI::FuzzyLogic::Permutator 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
242             $i = new AI::FuzzyLogic::Discriminator 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
243             $i = new AI::FuzzyLogic::Abstractor 'speed', 0.1, 0.3, 0.2, 0.1, 0.1;
244              
245             Beware! Once created, you'll need to change the combinational behavior frequently
246             to get any work done. Use the C, C,
247             C, and C methods to change the type of an
248             existing object.
249              
250             =cut
251              
252             sub new {
253              
254 0 0   0 1 0 my $class = shift; $class = ref $class if ref $class;
  0         0  
255              
256 0 0       0 $class = 'AI::FuzzyLogic::Abstractor' if $class eq 'AI::FuzzyLogic'; # default to this subclass
257              
258 0         0 my $self = bless [], $class;
259              
260 0         0 $self->add_subsets(@_);
261              
262 0         0 $self;
263              
264             }
265              
266             #
267             # accessors
268             #
269              
270             =head3 add_subsets
271              
272             Just like C, but adds new subsets to an existing set.
273              
274             $set->add_subsets(new AI::FuzzyLogic 'foo', 0.0, 0.1, 0.1, 0.1, 0.0);
275              
276             Newly added subsets retain their type in the new object (though the output of
277             an operation against an Abstractor is always a single set of type 'abstract').
278              
279             C calls this method to do its dirty work.
280              
281             =cut
282              
283             sub add_subsets {
284              
285             # AI::FuzzyLogic objects are containers of AI::FuzzyLogic::Subset objects.
286             # this method adds a new AI::FuzzyLogic::Subset object to our list.
287             # it may have to create one first, if all of the args are numeric.
288             # if any of the arguments are AI::FuzzyLogic objects, we must extract the subsets from it.
289             # if we get an array ref, we bless it into a new AI::FuzzyLogic::Subset.
290             # if any given arg is already an AI::FuzzyLogic::Subset, we add it directly to our list.
291             # new() uses this to make sense of its arguments, and it is available for use directly as well.
292              
293 0     0 1 0 my $me = shift;
294              
295 0 0       0 if(! grep ref $_, @_ ) {
296             # they're all non-reference types
297             # print "debug: ", scalar grep({ ! ref $_ } @_), " and ", scalar(@_), "\n";
298 0 0       0 my $type = shift or die "add_subsets() all non reference case - expecting type string as first arg";
299 0         0 push @$me, AI::FuzzyLogic::Subset->new($type, [@_]);
300 0         0 return $me;
301             }
302              
303 0         0 foreach my $i (@_) {
304              
305 0 0 0     0 push @$me, $i if blessed($i) and $i->isa('AI::FuzzyLogic::Subset');
306 0 0 0     0 push @$me, $i->unwrap() if blessed($i) and $i->isa('AI::FuzzyLogic');
307 0 0       0 push @$me, AI::FuzzyLogic::Subset->new('unknown', [map { $_ } @$i]) if ref $i eq 'ARRAY';
  0         0  
308              
309             }
310              
311 0         0 return $me;
312              
313             }
314              
315             *add_subset = *add = *add_subsets;
316              
317 1     1   1935 sub import { return 1; }
318              
319             =head3 query_type
320              
321             $set->query_type('speed');
322              
323             Return the subsets (AI::FuzzyLogic::Subset objects) of a given type ('speed', in
324             this example. In scalar context, the first is returned. In list context, all
325             matching subsets are returned. This allows access to subsets directly minipulate
326             them. This can be used with the constructor to build a new AI::FuzzyLogic object
327             containing all subsets of a given type:
328              
329             $speeds = new AI::FuzzyLogic $old_set->query_type('speed');
330              
331             Returns C if none are found.
332              
333             =cut
334              
335             sub query_type {
336              
337 0     0 1   my $me = shift;
338 0           my $type = shift;
339              
340 0           my @sets = grep { $_->type() eq $type } ARRAY $me;
  0            
341 0 0         return @sets if wantarray();
342 0 0         return $sets[0] if @sets;
343 0           return undef;
344              
345             }
346              
347             =head3 unwrap
348              
349             @subsets = $set->unwrap();
350              
351             Return all subsets from a set. These may be used to construct new sets, or
352             they may be individually minipulated (type changed, perhaps). Some
353             operators mutate (change the existing object) while others return new
354             objects that reflect the changes. The former case will affect the state of
355             the set from which the subset was obtained, and the latter won't.
356              
357             Handy for debugging:
358              
359             foreach my $i ($set->unwrap()) {
360             print "in set: ", $i->type(), "\n";
361             }
362              
363             Subsets also have an C method that returns an array of scalar
364             floating point values that describe the set.
365              
366              
367             =cut
368              
369             sub unwrap {
370              
371             # all of our little Subsets
372             # currently, this object is a blessed arrayref, where each element is a subset.
373             # this may change in the future. for now, all we have to do is reference ourselves
374             # to get our list of subsets.
375             # it has come to my attention that this needs to be used far too often to do
376             # routine work. attempting to fix API.
377              
378 0     0 1   my $me = shift;
379              
380 0           return ARRAY $me;
381              
382             }
383              
384             sub set {
385 0     0 0   my $me = shift;
386             # 2007 new
387 0           return $me->[0]->set;
388             }
389              
390             sub name {
391 0     0 0   my $me = shift;
392             # 2007 new
393 0           return $me->[0]->name;
394             }
395              
396             sub type {
397 0     0 1   my $me = shift;
398             # 2007 bugfix for documentation... using this method won't make sense in a lot of cases but should right after a discriminator operation or abstractor operation where there's one set left
399 0           return $me->[0]->type;
400             }
401              
402             sub subtypes {
403              
404             # we have an as_... method for each of our subtypes. each subtype adds its own as_...
405             # method to the base class. these methods rebless the object, changing it from one
406             # subtype to another. since they are put into the base class, any subtype cass be
407             # converted to any other subtype by calling the right as_... method.
408             # this method looks through the symbol table to find out which as_... methods have
409             # actually been added, and returns the list of names of them.
410              
411 0     0 0   grep { m/^as_/ } keys %{__PACKAGE__.'::'};
  0            
  0            
412              
413             }
414              
415             =head3 change_type
416              
417             $bar->change_type('abstract', 'foo'); # change result from "abstract" to "foo" type
418              
419             To make the combinational magic specified by Combinational Behavior work, types
420             must match up. This means frequently having to change the type of a subset in a set.
421             Volts may go to ampres to watts, and will need to be renamed at each step. If
422             sets with only one subset are used, it may be easier to just make all sets into
423             Permutators:
424              
425             my $juice = AI::FuzzyLogic::new('juice', 0.5, 0.5, 0.5)->as_permutator();
426              
427             This, and the result of all operations on which it is on the left hand side of,
428             will all combine freely with other types. Otherwise, you'd eventually have
429             to do:
430              
431             $juice->change_type('juice', 'watts');
432              
433             Beware! Type is completely different than combinational behavior. Type
434             controls how things combine, but the rules ultimately depend on
435             the combinational behavior of the object on the left of the operation.
436             Start with the description of the combinational behavior (Abstractor,
437             Permutator, Discriminator, Correlator) and read how it uses type
438             information.
439              
440             =cut
441              
442             sub change_type {
443              
444             # change subsets of one type to another type. this is often required
445             # to control combinational behavior.
446              
447 0     0 1   my $me = shift;
448 0 0         my $oldtype = shift() or die "old type (string) required";
449 0 0         my $newtype = shift() or die "new type (string) required";
450 0           my $count = 0;
451              
452 0           foreach my $i (ARRAY $me) {
453 0 0         if($i->type() eq $oldtype) {
454 0           $i->type($newtype);
455 0           $count++;
456             }
457             }
458              
459 0           return $count;
460              
461             }
462              
463             #
464             # conversion
465             #
466              
467             sub stringify {
468              
469             # generate pretty little text graph for each subset in our collection
470             # part of overload interface to the world - attempting to convert to string
471             # and print a fuzzy object makes the overload interface call this.
472             # using a fuzzy object with the . operator or using it inside of a "" string
473             # triggers this.
474              
475 0     0 0   my $me = shift;
476              
477 0           my $ret;
478              
479 0           foreach my $subset (ARRAY $me) {
480 0           $ret .= $subset->stringify();
481             }
482              
483 0           return $ret;
484              
485             }
486              
487             sub numify {
488              
489             # find the center of mass of the curve for each set.
490             # part of overload interface to the world.
491             # this isn't a meaningful operation for sets that contain more than one subset -
492             # things kind of get combined in a non-helpful way.
493             # using other operations, distill all of the data down to single sets,
494             # then use this to do "crisp" poerations between this distilled data.
495             # fuzzy data must be made crisp at some point if it is to be used in non
496             # fuzzy systems. it can be done by converting it to a number, or doing some
497             # test between two fuzzy sets that yeilds a yes/no answer.
498              
499             # comments to self:
500              
501             # this doesn't make any sense, using centroid_inner(). changes to mean_inner().
502             # centroid is a function of left-rightness. unless all subsets describe the same thing,
503             # this is meaningless. if requesting a number, the user is probably interested in overall fit,
504             # which would mean just average of each set, all averaged together.
505              
506             # this doesn't make sense averaging the subsets volumes. if the correlator fails to find
507             # matching subset types, something could actually rank higher than a case where all subtimes
508             # match up. better to sum the averages.
509              
510             # more comments to self:
511              
512             # duh, it does make sense. people need two means of defuzzifying: centroid and mean.
513             # centroid does left-right, mean does up-down.
514              
515 0     0 0   my $me = shift;
516              
517 0           my $total;
518              
519             #if(scalar ARRAY $me > 1) {
520             # warn "Attempting to convert fuzzy subsets to a number: which subset do you want? Use a " .
521             # "discriminator to select only one.";
522             # return 0;
523             #}
524              
525 0           foreach my $subset (ARRAY $me) {
526 0           $total += $subset->centroid_inner();
527             }
528              
529 0 0         return undef unless $total;
530              
531 0           return $total / scalar ARRAY $me;
532              
533             }
534              
535             sub numify2 {
536 0     0 0   my $me = shift;
537 0           my $total;
538 0           foreach my $subset (ARRAY $me) {
539 0           $total += $subset->mean_inner();
540             }
541 0 0         return undef unless $total;
542 0           return $total / scalar ARRAY $me;
543             }
544              
545             #
546             # operators
547             #
548              
549             # the fuzzy operations that can be performed defines fuzzy logic, and is the heart of this
550             # module. unary operators are delegated to the subset itself. binary operators are
551             # done here. the definition of the actual operation is here, but a lot of infrastructure
552             # is called upon.
553              
554             #
555             # unary operations
556             #
557              
558             # each of these creates a new AI::FuzzyLogic object of the same subtype as the current
559             # object, containing the result of applying the operation to each subset in the current object.
560             # the results of applying these operations should all be copies, not references to originals.
561              
562 0     0 0   sub bneg { $_[0]->new(map({ $_->bneg() } ARRAY $_[0])) }
  0            
563 0     0 0   sub bpls { $_[0]->new(map({ $_->bpls() } ARRAY $_[0])) }
  0            
564 0     0 0   sub bpow { $_[0]->new(map({ $_->bpow() } ARRAY $_[0])) }
  0            
565 0     0 0   sub bmns { $_[0]->new(map({ $_->bmns() } ARRAY $_[0])) }
  0            
566 0     0 0   sub brsh { $_[0]->new(map({ $_->brsh() } ARRAY $_[0])) }
  0            
567 0     0 0   sub blsh { $_[0]->new(map({ $_->blsh() } ARRAY $_[0])) }
  0            
568              
569             # except this one, which is a mutator, and changes the originals.
570              
571 0     0 0   sub balance { foreach my $subset (ARRAY $_[0]) { $subset->balance() }; $_[0]; }
  0            
  0            
572              
573             #
574             # utilities for use by binary operators
575             #
576              
577             sub compile {
578              
579             # given two subsets and a callback, callback with each set of paired numbers inside those subsets.
580             # this stretches out the smaller set to be as large as the larger set, for the purpose of doing
581             # element-by-element comparisons.
582              
583             # see the notes at the end of this file on how this fits in.
584              
585 0 0   0 0   my $inta = shift; die 'wtf' unless $inta->isa('AI::FuzzyLogic::Subset');
  0            
586 0 0         my $intb = shift; die 'wtf' unless $intb->isa('AI::FuzzyLogic::Subset');
  0            
587              
588 0           my $callback = shift;
589              
590 0 0         my $seta = $inta->set() or die;
591 0 0         my $setb = $intb->set() or die;
592              
593 0           my $stepa; my $stepb; my $max;
  0            
594 0           my $posa = 0; my $posb = 0;
  0            
595 0           my @ret;
596              
597 0 0         if(scalar(ARRAY $seta) > scalar(ARRAY $setb)) {
598 0           $max = scalar(ARRAY $seta); $stepa = 1; $stepb = scalar(ARRAY $setb)/scalar(ARRAY $seta);
  0            
  0            
599             } else {
600 0           $max = scalar(ARRAY $setb); $stepb = 1; $stepa = scalar(ARRAY $seta)/scalar(ARRAY $setb);
  0            
  0            
601             }
602              
603 0   0       while($posa<$max && $posb<$max) {
604 0   0       $seta->[int $posa] ||= 0; # work around for a strange bug that generates warnings XXX
605 0   0       $setb->[int $posb] ||= 0; # work around for a strange bug that generates warnings XXX
606 0           push @ret, $callback->($seta->[int $posa], $setb->[int $posb]);
607 0           $posa += $stepa; $posb += $stepb;
  0            
608             }
609              
610 0   0       return AI::FuzzyLogic::Subset->new($inta->type() || 'unknown', \@ret);
611              
612             }
613              
614             #
615             # binary operators - discriminators
616             #
617              
618             # part of each of these are stock. see the comments at the end of the file about
619             # using selector() to narrow down which subsets should be combined, and compile()
620             # to do an element by element comparison between those two subsets.
621             # just pay attention to the line that starts with "return" - that is the heart
622             # of each operation, which is applied to each matching element between two subsetsw.
623              
624             #head3 best
625             #
626             #When used with Permutators, the best combination of subsets from the left and
627             #right is found. With Abstractors, you get a single output set representing
628             #the best combination. Perhaps most useful with the Descriminator, with one
629             #or two or many subsets in the set on the left and several on the right.
630             #The best match is found and the subset on the right returned in a new AI::FuzzyLogic object.
631             #
632             # $a->best($b);
633             #
634             #Right now, I don't think these work. Everything is included in the output, though
635             #some subsets are mangled to all-zeros or otherwise molested.
636             #
637             #cut
638              
639             sub best {
640 0     0 0   my $me = shift;
641             $me->selector(shift(), sub {
642             compile($_[0], $_[1], sub {
643             # $_[0] vs $_[1] ---- more points, the closer together each point
644             # how closely do two lines follow each other? like smallest() and largest() but without
645             # the "flunk" conditions for going over or under.
646 0           return 1.0 - abs($_[0] - $_[1]);
647 0     0     });
648 0           });
649             }
650              
651             sub smallest {
652 0     0 0   my $me = shift;
653             $me->selector(shift(), sub {
654 0     0     my $flunk = 0;
655             my $subset = compile($_[0], $_[1], sub {
656             # more points the closer each $_[0]->[x] is without going over $_[1]->[x]
657             # ie, the smaller one line is, the better. if it goes over, it flunks.
658             # 0 condition should 0 entire set, not just that segment - XXX
659 0 0         return (1 - ($_[1] - $_[0])) if $_[0] < $_[1];
660 0           $flunk = 1; return 0;
  0            
661 0           });
662 0 0         return $flunk ? AI::FuzzyLogic::Subset->new($_[0]->type(), [0, 0, 0, 0, 0]) : $subset;
663 0           });
664             }
665              
666             sub largest {
667 0     0 0   my $me = shift;
668             $me->selector(shift(), sub {
669 0     0     my $flunk = 0;
670             my $subset = compile($_[0], $_[1], sub {
671             # more point the closer each $_[0]->[x] is without going under $_[1]->[x]
672             # ie, larger the better, and cannot go under the second line.
673             # 0 condition should 0 entire set, not just that segment - XXX
674 0 0         return (1 - ($_[0] - $_[1])) if $_[0] > $_[1];
675 0           $flunk = 1; return 0;
  0            
676 0           });
677 0 0         return $flunk ? AI::FuzzyLogic::Subset->new($_[0]->type(), [0, 0, 0, 0, 0]) : $subset;
678 0           });
679             }
680              
681             =head3 larger
682              
683             $a->larger($b); # does $a completely encompass $b?
684              
685             Test if one set fits entirely within another or not. If there are multiple subsets
686             and combinational behavior and types allow, then it returns true if any matching
687             subsets on the left are larger than any on the right.
688              
689             Unlike the above, this is actually beleived to work and has been somewhat tested.
690              
691             =cut
692              
693             sub larger {
694 0     0 1   my $me = shift;
695 0           my $larger = 1;
696 0           my $any = 0;
697             $me->selector(shift(), sub {
698             compile($_[0], $_[1], sub {
699             # the left side must entirely contain the right side to return true
700 0           $any = 1;
701 0 0         $larger = 0 if $_[0] < $_[1];
702 0           return 0;
703 0     0     });
704 0           });
705 0 0         $any or die "no matching subtypes for object type " . ref($me);
706 0           return $larger;
707             }
708              
709             sub centroid {
710 0     0 0   my $me = shift;
711             $me->selector(shift(), sub {
712             # how different is the center of mass between two lines? the lines might have very different
713             # height and shape, but if their center of masses match exactly, we get a 1.0
714 0     0     return abs(centroid_inner($_[0]) - centroid_inner($_[1]));
715 0           });
716             }
717              
718             #
719             # binary operators - operators
720             #
721              
722             sub bmul {
723 0     0 0   my $me = shift;
724             $me->selector(shift(), sub {
725             compile($_[0], $_[1], sub {
726             # good for sensitivity control - amplify the importantance of some regions
727 0 0         return $_[0] * $_[1] > 1 ? 1 : $_[0] * $_[1];
728 0     0     });
729 0           });
730             }
731              
732             sub bdiv {
733 0     0 0   my $me = shift;
734             $me->selector(shift(), sub {
735             compile($_[0], $_[1], sub {
736             # good for sensitivity control - dimenish the importantance of some regions
737 0           return $_[0] / $_[1];
738 0     0     });
739 0           });
740             }
741              
742             sub badd {
743 0     0 0   my $me = shift;
744             $me->selector(shift(), sub {
745             compile($_[0], $_[1], sub {
746 0 0         return $_[0]+$_[1] > 1 ? 1 : $_[0]+$_[1];
747 0     0     });
748 0           });
749             }
750              
751             sub bsub {
752 0     0 0   my $me = shift;
753             $me->selector(shift(), sub {
754             compile($_[0], $_[1], sub {
755 0 0         return $_[0]-$_[1] > 0 ? $_[0]-$_[1] : 0;
756 0     0     });
757 0           });
758             }
759              
760             sub bcmp {
761             # compare volumes, to sort by largeness of set
762 0 0   0 0   my $me = shift(); $me->isa(__PACKAGE__) or die __PACKAGE__ . ' required';
  0            
763 0 0         my $them = shift()->numify(); $me->isa(__PACKAGE__) or die __PACKAGE__ . 'required';
  0            
764 0           return $me->numify2() <=> $them->numify2();
765             }
766              
767             sub bstrcmp {
768             # compare center of mass, to sort by position of hump (okey, that sounds bad)
769 0 0   0 0   my $me = shift(); $me->isa(__PACKAGE__) or die __PACKAGE__ . ' required';
  0            
770 0 0         my $them = shift()->numify(); $me->isa(__PACKAGE__) or die __PACKAGE__ . 'required';
  0            
771 0           return $me->numify() <=> $them->numify();
772             }
773              
774             sub band {
775 0     0 0   my $me = shift;
776             $me->selector(shift(), sub {
777             compile($_[0], $_[1], sub {
778             # the quentiscential fuzzy operation - intersection
779 0 0         return $_[0] < $_[1] ? $_[0] : $_[1];
780 0     0     });
781 0           });
782             }
783              
784             sub bior {
785 0     0 0   my $me = shift;
786             $me->selector(shift(), sub {
787             compile($_[0], $_[1], sub {
788 0 0         return $_[0] > $_[1] ? $_[0] : $_[1];
789 0     0     });
790 0           });
791             }
792              
793             sub bxor {
794 0     0 0   my $me = shift;
795 0           my $max;
796             $me->selector(shift(), sub {
797             compile($_[0], $_[1], sub {
798             # the degree that neither are true. same as an or then a negate, i guess.
799 0 0         return 1.0 - ( $_[0] > $_[1] ? $_[0] : $_[1] );
800 0     0     });
801 0           });
802             }
803              
804             sub bles {
805 0     0 0   my $me = shift;
806 0           my $lhs; my $rhs;
807             $me->selector(shift(), sub {
808             compile($_[0], $_[1], sub {
809             # does the first set have less area then the second? returns an integer.
810             # this implementation works - compile() stretches one out as needed.
811 0           $lhs += $_[0]; $rhs += $_[1];
  0            
812 0           return 0;
813 0     0     });
814 0           });
815 0           return $lhs < $rhs;
816             }
817              
818             sub bgre {
819 0     0 0   my $me = shift;
820 0           my $lhs = 0; my $rhs = 0;
  0            
821             $me->selector(shift(), sub {
822             compile($_[0], $_[1], sub {
823 0           $lhs += $_[0]; $rhs += $_[1];
  0            
824 0           return 0;
825 0     0     });
826 0           });
827 0           return $lhs > $rhs;
828             }
829              
830             sub bmod {
831 0     0 0   my $me = shift;
832 0           my $minfactor = undef;
833             $me->selector(shift(), sub {
834             compile($_[0], $_[1], sub {
835             # how many times does the set on the right fit into set on the left, for the smallest
836             # segment? untested.
837 0           my $tmp;
838 0 0         if($_[1]) {
839 0           $tmp = $_[0] % $_[1];
840 0 0         $minfactor = $tmp if ! defined $minfactor;
841 0 0         $minfactor = $tmp if $tmp < $minfactor;
842             } else {
843             # zero fits in an infinte number of times, even to zero. increase but don't decrease.
844 0 0         $minfactor = 10 if ! defined $minfactor;
845             }
846 0           return 0;
847 0     0     });
848 0           });
849 0           return $minfactor;
850             }
851              
852             1;
853              
854             #
855             # subclasses of AI::FuzzyLogic -
856             # versions of ourselves have different combinational behaviors
857             #
858              
859             # the base AI::FuzzyLogic set is never used directly. all sets are really a
860             # subclass. each subclass adds an as_* method to the base class, AI::FuzzyLogic,
861             # and each subclass defines its own private selector() method. this selector()
862             # method is the key difference between each subclass type, and it controls
863             # how subsets are permutated when two sets are compared against each other.
864             # read the POD that describes each behavior to understand the purpose of these
865             # subclasses. see the section about "Combinational Behavior".
866             # selector() is called from all "binary operators" - operators that require
867             # two arguments, one on the left, and one on the right. this is part of the
868             # overload interface. perl invokes the correct routine to deal with an operator
869             # being used on a fuzzy object. there is also a high level explanation at the
870             # end of the file, of how operators and selectors and subsets fit together.
871              
872             =head2 Combinational Behavior
873              
874             A set contains subsets. How the subsets of two sets interact when an operation is
875             performed is their combination behavior. Depending on what the data means and
876             how you're using it, you'll need to specify how things combine.
877              
878             =head3 Abstractors
879              
880             Abstractors always return exactly one set, which is meant to be a gross summary
881             of membership of one set in another.
882             Returns one set, with about as many members as there are subsets in the object on
883             the right. Gives a membership summery, or a composition of how well
884             or how poorly all of the various attributes match up, by type.
885             If the types don't match up, they are ignored. Otherwise, the comparision
886             of the matching sets forms a single segment in the output set. The
887             output set is balanced, with the line the highest in the center.
888             Useful when used between a set containing patterns to match and set containing
889             observations.
890              
891             For example, subsets may represent color and size. One set, "a", is observed
892             in the wild (the Internet, through data capture, what have you). Other sets,
893             "x1", "x2", "x3", etc, each having the same subsets (color and size) are compared
894             against "a" to find the best match in attempt to classify "a" as being
895             stereotypical of one of a few known cases.
896              
897             If the output is a flat zeros, no criteria matched. If it is a low curve,
898             few things matched, and they matched poorly. If it is a low curve with
899             some spikes in the middle, a few things matched well, but most criteria
900             matched poorly. A nice bell curve is a fairly good match on most criteria,
901             and a solid box with 1's across the board is a perfect fit.
902              
903             $set->as_abstractor();
904              
905             The result is always balanced (the hump, if any, is in the middle).
906              
907             The single result set contains exactly one subset, which is of type 'abstract'.
908             To do operations on that with anything other than C or a Permutator,
909             you'll need to change the type to match the desired subset type of the other set.
910              
911             my $foo = new AI::FuzzyLogic 'foo', 0.1, 0.2, 0.5, 0.2, 0.1;
912              
913             my $bar = $big_old_set->as_abstractor() & $another_big_old_set();
914              
915             $bar->change_type('abstract', 'foo'); # change result from "abstract" to "foo" type
916              
917             my $baz = $foo & $bar;
918              
919             Beware! Once created, you'll need to change the combinational behavior frequently
920             to get any work done. These C methods will need to be used over and over.
921              
922             =cut
923              
924             package AI::FuzzyLogic::Abstractor;
925 1     1   3502 use base 'AI::FuzzyLogic';
  1         2  
  1         410  
926 0     0 0   sub AI::FuzzyLogic::as_abstractor { bless $_[0], __PACKAGE__; }
927             sub selector {
928 0     0     my $me = shift;
929 0 0         my $them = shift; $them->isa('AI::FuzzyLogic') or die 'instance of AI::FuzzyLogic needed';
  0            
930 0 0         my $coderef = shift; ref $coderef eq 'CODE' or die 'no coderef';
  0            
931 0           my @newset;
932 0           foreach my $mysubset ($me->unwrap()) {
933 0           foreach my $theirsubset ($them->unwrap()) {
934 0 0         next unless $mysubset->type() eq $theirsubset->type();
935 0           push @newset, $coderef->($mysubset, $theirsubset);
936             }
937             }
938             # for each subset, find the average of all segments; these averages, balanced, are our new set of one subset
939 0           return $me->new('abstract', sort map { $_->mean_inner() } @newset)->balance();
  0            
940             }
941             1;
942              
943             =head3 Discriminators
944              
945             Discriminators pare down sets which have subsets.
946              
947             Discriminators consider all of the permutations, but throw away all of them except
948             the set from the right-hand-side which yeildeds the largest resulting set
949             (defined by volume). Hence, whichever operation is performed on a discriminator
950             only serves to give a criteria for selecting a set from the right-hand-side.
951             Discriminators are useful for selecting one optimial case from a number of alternatives.
952             Like the permutator, except we only keep the highest ranked cross matches.
953             Always returns exactly one set from the right hand side. The left hand side is considered
954             to be the rule by which to measure the left.
955              
956             $set->as_discriminator();
957              
958             =cut
959              
960             package AI::FuzzyLogic::Discriminator;
961 1     1   7 use base 'AI::FuzzyLogic';
  1         1  
  1         295  
962 0     0 0   sub AI::FuzzyLogic::as_discriminator { bless $_[0], __PACKAGE__; }
963             sub selector {
964 0     0     my $me = shift;
965 0 0         my $them = shift; $them or die; $them->isa('AI::FuzzyLogic') or die 'instance of AI::FuzzyLogic needed';
  0 0          
  0            
966 0 0         my $coderef = shift; ref $coderef eq 'CODE' or die 'no coderef';
  0            
967              
968 0           my $highestrankedset;
969             my $highestrankedvalue;
970 0           my @ret;
971              
972 0           foreach my $mysubset ($me->unwrap()) {
973 0           $highestrankedvalue = 0;
974 0           $highestrankedset = undef;
975 0           foreach my $theirsubset ($them->unwrap()) {
976 0           my $value = $coderef->($mysubset, $theirsubset)->mean_inner();
977 0 0         if($value > $highestrankedvalue) {
978 0           $highestrankedvalue = $value;
979 0           $highestrankedset = $theirsubset;
980             }
981             }
982             # push @ret, $highestrankedset->clone() if $highestrankedset; # XXX there is no clone()!
983 0 0         push @ret, $highestrankedset if $highestrankedset;
984             }
985 0 0         return unless @ret;
986 0           return $me->new(@ret);
987             }
988             1;
989              
990             =head3 Permutators
991              
992             Permutators consider every possible permutation between subsets in the object on
993             the left-hand-side and the subsets in the object on the right-hand-side, and return
994             an object with a subset for each permutation. Performs the desired operation
995             as a cartesian product.
996              
997             $set->as_permutator();
998              
999             =cut
1000              
1001             package AI::FuzzyLogic::Permutator;
1002 1     1   6 use base 'AI::FuzzyLogic';
  1         2  
  1         254  
1003 0     0 0   sub AI::FuzzyLogic::as_permutator { bless $_[0], __PACKAGE__; }
1004             sub selector {
1005             # given two objects and a code ref, find cartesian products with coderef performed on them.
1006 0     0     my $me = shift;
1007 0 0         my $them = shift; $them->isa('AI::FuzzyLogic') or die 'instance of AI::FuzzyLogic needed';
  0            
1008 0 0         my $coderef = shift; ref $coderef eq 'CODE' or die 'no coderef';
  0            
1009 0           my @ret;
1010 0           foreach my $mysubset ($me->unwrap()) {
1011 0           foreach my $theirsubset ($them->unwrap()) {
1012 0           push @ret, $coderef->($mysubset, $theirsubset);
1013             }
1014             }
1015 0 0         return undef unless @ret;
1016 0           return $me->new(@ret);
1017             }
1018             1;
1019              
1020             =head3 Correlators
1021              
1022             Correlators are like Permutators, except instead of considering all permutations,
1023             they only consider permutations between subsets with matching unit types.
1024             Permutators and Correlators are useful for generating alternative cases, possibly in
1025             several steps, which Discriminators or Abstractors may then select from.
1026              
1027             Useful for finding optimal cases. For example, combinations of two or more
1028             gears can be considered, and then in an additional step, the combination best
1029             matching some criteria could be selected.
1030              
1031             $set->as_correlator();
1032              
1033             =cut
1034              
1035             package AI::FuzzyLogic::Correlator;
1036 1     1   12 use base 'AI::FuzzyLogic';
  1         3  
  1         269  
1037 0     0 0   sub AI::FuzzyLogic::as_correlator { bless $_[0], __PACKAGE__; }
1038             sub selector {
1039             # perform operations between matching type subsets between two objects
1040 0     0     my $me = shift;
1041 0 0         my $them = shift; $them->isa('AI::FuzzyLogic') or die 'instance of AI::FuzzyLogic needed';
  0            
1042 0 0         my $coderef = shift; ref $coderef eq 'CODE' or die 'no coderef';
  0            
1043 0           my @ret;
1044 0           foreach my $mysubset ($me->unwrap()) {
1045 0           foreach my $theirsubset ($them->unwrap()) {
1046 0 0         next unless $mysubset->type() eq $theirsubset->type();
1047 0           push @ret, $coderef->($mysubset, $theirsubset);
1048             }
1049             }
1050 0 0         return undef unless @ret;
1051 0           return $me->new(@ret);
1052             }
1053             1;
1054              
1055             __END__