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__ |