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