File Coverage

blib/lib/AI/FuzzyLogic/Subset.pm
Criterion Covered Total %
statement 3 116 2.5
branch 0 48 0.0
condition n/a
subroutine 1 20 5.0
pod 0 19 0.0
total 4 203 1.9


line stmt bran cond sub pod time code
1             #
2             # AI::FuzzyLogic::Subset
3             #
4              
5             #
6             # wrapper for individual subsets
7             #
8              
9             package AI::FuzzyLogic::Subset;
10              
11             our $VERSION = '0.01';
12              
13             our $newname = "FuzzySet00000000"; # static variable
14              
15 1     1   6 use English::Reference;
  1         1  
  1         1568  
16              
17             sub new {
18 0     0 0   my $class = shift;
19 0 0         my $type = shift or die 'first arg to constructor is unit type string';
20 0 0         my $set = shift; ref $set eq 'ARRAY' or die 'second arg to constructor is array reference to a set';
  0            
21 0           bless [$set, $type, $newname++], $class;
22             }
23              
24             sub clone {
25 0 0   0 0   my $self = shift; $self->isa(__PACKAGE__) or die;
  0            
26 0           bless [[ARRAY $self->set()], $self->type(), $newname++], ref $self;
27             }
28              
29 0 0   0 0   sub set :lvalue { $_[0]->[0] = $_[1] if @_ == 2; $_[0]->[0]; };
  0            
30 0 0   0 0   sub type :lvalue { $_[0]->[1] = $_[1] if @_ == 2; $_[0]->[1]; };
  0            
31 0 0   0 0   sub name :lvalue { $_[0]->[2] = $_[1] if @_ == 2; $_[0]->[2]; };
  0            
32              
33             #
34             # utility routines
35             #
36              
37             sub bing {
38              
39             # put a little dent into the curve, centered at a given location
40              
41 0     0 0   my $self = shift;
42 0           my $set = $self->set();
43 0           my $location = shift;
44 0           my $degree = shift;
45              
46             # make sure we don't try to plot off the side of the graph, and center location
47 0           my $halfsize = scalar ARRAY $set; $halfsize += 0.5; $halfsize /= 2;
  0            
  0            
48 0           $location += $halfsize;
49              
50 0           $set->[$location] = $degree;
51 0 0         $set->[$location - 1] += 0.75 * $degree if $location - 1 >= 0;
52 0 0         $set->[$location + 1] += 0.75 * $degree if $location + 1 < scalar ARRAY $set;
53 0 0         $set->[$location - 2] += 0.33 * $degree if $location - 2 >= 0;
54 0 0         $set->[$location + 2] += 0.33 * $degree if $location + 2 < scalar ARRAY $set;
55              
56 0           $self->normalize();
57              
58 0           return $self;
59              
60             }
61              
62             sub unwrap {
63              
64             # dump out the numerical contents of our set. this is currently essentially a no-op:
65             # it is already stored as an array of numbers. in the future, it may be a string,
66             # or it may be PDL, or it may be something else.
67              
68 0     0 0   my $self = shift;
69 0 0         my $set = $self->() or die "set missing. shouldn't happen.";
70              
71 0 0         return ARRAY $set if wantarray();
72 0           return $set;
73              
74             }
75              
76             sub wrap {
77              
78             # like above, but put data in. presently a no-op, but would translate to
79             # internal format where the internal format to change.
80              
81 0     0 0   my $self = shift;
82 0 0         my @set = @_ or die "set data required";
83              
84 0           $self->set(\@set);
85 0           1;
86              
87             }
88              
89             #
90             # inner routines
91             #
92              
93             sub normalize {
94              
95             # normalize to 1.0 being the max value for any slot if any slot is over 1.0
96              
97 0     0 0   my $self = shift();
98 0           my $set = $self->set();
99              
100 0           my $max;
101 0 0         foreach my $i (ARRAY $set) { $max = $i if($max<$i); }
  0            
102 0 0         if($max>1) { foreach my $i (ARRAY $set) { $i/=$max; } }
  0            
  0            
103              
104 0           $self;
105              
106             }
107              
108             sub balance {
109              
110             # move the largest segments to the center, tapering outwards towards the sides.
111             # fixes sets that are off-center, and makes rounded curves out of jagged ones.
112             # real scientific, i know.
113              
114 0     0 0   my $self = shift();
115 0           my $set = $self->set();
116              
117 0           my @set = sort { $a <=> $b } ARRAY $set;
  0            
118 0           for(my $i=$#set;$i>0;$i-=2) { push @set, splice @set, $i, 1 }
  0            
119              
120 0 0         if(@set == 3) {
121             # that doesn't work for very small sets. fudge it. kinda like a little bubble sort.
122 0 0         ($set[1], $set[2]) = ($set[2], $set[1]) if $set[2] > $set[1];
123             }
124              
125 0           $self->set() = \@set;
126              
127 0           $self;
128              
129             }
130              
131             sub centroid_inner {
132              
133             # find center of volume
134              
135 0     0 0   my $self = shift();
136 0           my $set = $self->set();
137              
138 0           my $left = 0;
139 0           my $half = 0.0;
140 0           my $index = 0;
141              
142 0           map { $half += $_ } ARRAY $set; $half /= 2;
  0            
  0            
143              
144 0           foreach my $i (ARRAY $set) {
145 0 0         if($left+$i >= $half) {
146             # compute (interpolate) the fraction between fenceposts
147 0           $half -= $left;
148             # $index+($half/$i) adds on a portion of the current bar depending how far $half is through it
149             # -0.5 moves us from counting fenceposts to being centered on numbers themselves.
150             # dividing that by the size of the set gives us a number such that: 0.0 <= $num <= 1.0.
151 0 0         return $index - 0.5 unless($i); # degenerate case...
152 0           return ($index+($half/$i)-0.5)/scalar(@$set);
153             }
154 0           $index++;
155 0           $left += $i;
156             }
157              
158             }
159              
160             sub mean_inner {
161              
162             # average height of all of the segments
163              
164 0     0 0   my $self = shift();
165 0 0         my $set = $self->set() or die 'no fuzzy set';
166              
167 0           my $samples;
168             my $avg;
169              
170 0           foreach my $i (ARRAY $set) {
171 0           $avg += $i; $samples++;
  0            
172             }
173              
174 0 0         return $avg/$samples if($samples);
175              
176             }
177              
178             sub stringify {
179              
180 0     0 0   my $self = shift;
181 0 0         my $set = $self->set() or die 'no fuzzy set';
182 0 0         return '[empty set]' unless scalar ARRAY $set;
183 0           my $inc = 79/scalar(ARRAY $set);
184 0           my $ret .= $self->type() . "\n";
185              
186 0           for(my $y=0.9;$y>0;$y-=0.1) {
187 0           for(my $x=0;$x
188 0           $ret .= (' ', '*')[$set->[$x] > $y] x scalar $inc;
189             }
190 0           $ret .= "\n";
191             }
192              
193 0           return $ret;
194              
195             }
196              
197             #
198             # unary operators
199             #
200              
201             # there are some docs at the end of AI::FuzzyLogic.pm, in the source code,
202             # on how these are implemented. the code style is dense and anti-redundancy.
203             # unlike mutator methods, these return new objects. this is consistent with
204             # the requirements of the operator overloading interface. we assume that the
205             # existing object continues to exist, even though it may be immediately
206             # assigned over top of and dereferenced.
207              
208             sub bneg {
209              
210             # negate set
211              
212 0     0 0   my $self = shift;
213 0           my $set = $self->set();
214 0           my @ret = ();
215              
216 0           foreach my $i (ARRAY $set) {
217 0           push @ret, 1.0 - $i;
218             }
219              
220 0           __PACKAGE__->new($self->type(), \@ret);
221             }
222              
223             sub bpow {
224              
225             # take each value to a given power then normalize again.
226             # with a value <1, this serves to de-exagerate the curve, moving the line twards the middle.
227             # with a value >1, this exagerates the curve, causing it to tend twards the top and bottom.
228              
229 0     0 0   my $self = shift;
230 0           my $fac = shift;
231              
232 0           my @ret = ARRAY $self->set();
233              
234 0           foreach my $i (@ret) {
235 0           $i **= $fac;
236             }
237              
238 0           return __PACKAGE__->new($self->type(), \@ret)->normalize();
239              
240             }
241              
242             sub bpls {
243              
244             # normalize the curve to 1.0
245             # this is a mutator - we change ourself
246              
247 0     0 0   my $self = shift;
248 0           $self->normalize();
249 0           $self;
250              
251             }
252              
253             sub bmns {
254              
255             # trim the 0's off the beginning and the end - this serves to stretch the curve across the
256             # entire range.
257             # this is a mutator - we change ourself
258              
259 0     0 0   my $self = shift;
260 0           my $set = $self->set();
261              
262 0           my @newset;
263              
264 0           foreach my $i (ARRAY $set) {
265 0 0         if($i != 0 .. $i != 0) {
266             # starts being true as soon as we pass the 0's, then keeps going till we hit a 0
267 0           push @newset, $i;
268             };
269             }
270              
271 0           $self->set() = \@newset;
272 0           $self;
273              
274             }
275              
276 0     0 0   sub brsh {
277             }
278              
279 0     0 0   sub blsh {
280             }
281              
282             1;