File Coverage

blib/lib/AI/FuzzyEngine/Variable.pm
Criterion Covered Total %
statement 124 142 87.3
branch 17 22 77.2
condition 1 3 33.3
subroutine 31 31 100.0
pod 0 13 0.0
total 173 211 81.9


line stmt bran cond sub pod time code
1             package AI::FuzzyEngine::Variable;
2            
3 3     3   2008 use 5.008009;
  3         10  
  3         135  
4 3     3   19 use version 0.77; our $VERSION = version->declare('v0.2.2');
  3         70  
  3         23  
5            
6 3     3   276 use strict;
  3         6  
  3         107  
7 3     3   16 use warnings;
  3         3  
  3         96  
8 3     3   15 use Scalar::Util qw( blessed looks_like_number );
  3         4  
  3         228  
9 3     3   16 use List::MoreUtils;
  3         4  
  3         87  
10 3     3   15 use Carp;
  3         10  
  3         187  
11            
12 3     3   674 use AI::FuzzyEngine::Set;
  3         6  
  3         4311  
13            
14             my $set_class = _class_of_set();
15            
16             sub new {
17 18     18 0 22317 my ($class, $fuzzyEngine, @pars) = @_;
18 18         76 my $self = bless {}, $class;
19            
20             # check and store the assigned fuzzyEngine
21 18         34 my $fe_class = 'AI::FuzzyEngine';
22 18 50 33     210 croak "fuzzyEngine is not a $fe_class"
23             unless blessed $fuzzyEngine && $fuzzyEngine->isa($fe_class);
24 18         69 $self->{fuzzyEngine} = $fuzzyEngine;
25            
26             # load pars, create sets etc.
27 18         68 $self->_init(@pars);
28            
29 18         85 return $self;
30             };
31            
32 155     155 0 863 sub is_internal { shift->{is_internal} }
33 137     137 0 857 sub from { shift->{from} };
34 137     137 0 540 sub to { shift->{to} };
35 34     34 0 214 sub sets { shift->{sets} };
36 1     1 0 6 sub set_names { @{shift->{set_names}} };
  1         24  
37             sub set {
38 5     5 0 11 my ($self, $set_name) = @_;
39 5         15 return $self->{sets}{$set_name};
40             };
41 141     141 0 1682 sub fuzzyEngine { shift->{fuzzyEngine} };
42            
43             sub is_valid_set {
44 4     4 0 17 my ($self, $set_name) = @_;
45             # Should be simplified to exists $self->{sets}{$set_name}
46 4     6   17 return List::MoreUtils::any { $_ eq $set_name } keys %{ $self->sets };
  6         27  
  4         11  
47             }
48            
49             sub fuzzify {
50 6     6 0 67 my ($self, $val) = @_;
51 6 100       16 croak "Fuzzification not allowed for internal variables"
52             if $self->is_internal;
53 5         6 for my $set (values %{ $self->sets } ) {
  5         10  
54 13         37 $set->fuzzify( $val );
55             };
56 5         11 return;
57             }
58            
59             sub defuzzify {
60 5     5 0 45 my ($self) = @_;
61 5 100       14 croak "Defuzzification not allowed for internal variables"
62             if $self->is_internal;
63            
64 4         6 my @sets = values %{$self->sets};
  4         9  
65 4         9 my @funs = map { $_->memb_fun } @sets;
  9         22  
66 4         8 my @degrees = map { $_->degree } @sets;
  9         24  
67            
68             # If all degrees are real scalars a shortcut is possible
69 4 50       13 if (_non_is_a_piddle(@degrees)) {
70 4         13 my $funs = _clipped_funs( \@funs, \@degrees);
71 4         24 my $fun_agg = $set_class->max_of_funs( @$funs );
72 4         21 my $c = $set_class->centroid( $fun_agg );
73 4         43 return $c;
74             };
75            
76             # Need a function of my FuzzyEngine
77 0         0 my $fe = $self->fuzzyEngine;
78 0 0       0 die 'Internal: fuzzy_engine is lost' unless $fe;
79            
80             # Unify dimensions of all @degrees (at least one is a pdl)
81 0         0 my @synched_degrees = $fe->_cat_array_of_piddles(@degrees)->dog;
82 0         0 my @dims_to_reshape = $synched_degrees[0]->dims;
83            
84             # Make degrees flat to proceed them as lists
85 0         0 my @flat_degrees = map {$_->flat} @synched_degrees;
  0         0  
86 0         0 my $flat_degrees = PDL::cat( @flat_degrees );
87            
88             # Proceed degrees of @sets as synchronized lists
89 0         0 my @degrees_per_el = $flat_degrees->transpose->dog;
90 0         0 my @defuzzified;
91 0         0 for my $ix (reverse 0..$#degrees_per_el) {
92 0         0 my $el_degrees = $degrees_per_el[$ix];
93             # The next two lines cost much (75% of defuzzify)
94 0         0 my $funs = _clipped_funs( \@funs, [$el_degrees->list] );
95 0         0 my $fun_agg = $set_class->max_of_funs( @$funs );
96            
97 0         0 my $c = $set_class->centroid( $fun_agg );
98 0         0 $defuzzified[$ix] = $c;
99             };
100            
101             # Build result in shape of unified membership degrees
102 0         0 my $flat_defuzzified = PDL->pdl( @defuzzified );
103 0         0 my $defuzzified = $flat_defuzzified->reshape(@dims_to_reshape);
104 0         0 return $defuzzified;
105             }
106            
107             sub _clipped_funs {
108             # Clip all membership functions of a variable
109             # according to the respective membership degree (array of scalar)
110 4     4   6 my ($funs, $degrees) = @_;
111 4         7 my @funs = @$funs; # Dereferencing here saves some time
112 4         9 my @degrees = @$degrees;
113             my @clipped = List::MoreUtils::pairwise {
114 9     9   37 $set_class->clip_fun($a => $b)
115 4         30 } @funs, @degrees;
116 4         26 return \@clipped;
117             }
118            
119             sub reset {
120 12     12 0 1391 my ($self) = @_;
121 12         16 $_->reset() for values %{$self->sets};
  12         24  
122 12         36 return $self;
123             }
124            
125             sub change_set {
126 4     4 0 72 my ($self, $setname, $new_memb_fun) = @_;
127 4         18 my $set = $self->set( $setname );
128            
129             # Some checks
130 4 100       34 croak "Set $setname does not exist" unless defined $set;
131 3 100       11 croak 'Variable is internal' if $self->is_internal;
132            
133             # Convert to internal representation
134 2         7 my $fun = $self->_curve_to_fun( $new_memb_fun );
135            
136             # clip membership function to borders
137 2         8 $set->set_x_limits( $fun, $self->from => $self->to );
138            
139             # Hand the new function over to the set
140 2         7 $set->replace_memb_fun( $fun );
141            
142             # and reset the variable
143 2         6 $self->reset;
144 2         4 return;
145             }
146            
147             sub _init {
148 18     18   123 my ($self, @pars) = @_;
149            
150 18 50       59 croak "Too few arguments" unless @pars >= 2;
151            
152             # Test for internal variable
153 18         29 my ($from, $to, @sets);
154 18 100       72 if (looks_like_number $pars[0]) {
155             # $from => $to is given
156 15         36 $self->{is_internal} = '';
157 15         76 ($from, $to, @sets) = @pars;
158             }
159             else {
160 3         8 $self->{is_internal} = 1;
161 3         9 ($from, $to, @sets) = (undef, undef, @pars);
162             };
163            
164             # Store $from, $to ( undef if is_internal)
165 18         33 $self->{from} = $from;
166 18         32 $self->{to } = $to;
167            
168             # Provide names of sets in correct order by attribute set_names
169 18         28 my $ix = 1;
170 18         41 $self->{set_names} = [ grep {$ix++ % 2} @sets ];
  278         425  
171            
172            
173             # Build sets of the variable
174 18         140 my %sets = @sets;
175             SET_TO_BUILD:
176 18         69 for my $set_name (keys %sets) {
177            
178 139         390 my $fun = [ [] => [] ]; # default membership function
179            
180 139 100       389 if (not $self->is_internal) {
181             # Convert from set of points to [ \@x, \@y ] format
182 132         230 my $curve = $sets{$set_name};
183 132         273 $fun = $self->_curve_to_fun( $curve );
184            
185             # clip membership function to borders
186 132         372 $set_class->set_x_limits( $fun, $self->from => $self->to );
187             };
188            
189             # create a set and store it
190 139         329 my $set_class = $self->_class_of_set();
191 139         462 my $set = $set_class
192             ->new( fuzzyEngine => $self->fuzzyEngine,
193             variable => $self,
194             name => $set_name,
195             memb_fun => $fun, # [ [] => [] ] if is_internal
196             );
197 139         387 $self->{sets}{$set_name} = $set;
198            
199             # build membership function if necessary
200 139 100       1139 next SET_TO_BUILD if $self->can( $set_name );
201             my $method = sub {
202 58     58   2629 my ($variable, @vals) = @_; # Variable, fuzzy values
203 58         101 my $set = $variable->{sets}{$set_name};
204 58         157 return $set->degree( @vals );
205 111         488 };
206            
207             # register the new method to $self (the fuzzy variable)
208 3     3   28 no strict 'refs';
  3         5  
  3         1336  
209 111         216 *{ $set_name } = $method;
  111         449  
210             };
211             }
212            
213             sub _non_is_a_piddle {
214 9     9   21 return List::MoreUtils::none {ref $_ eq 'PDL'} @_;
  4     4   18  
215             }
216            
217             # Might change for Variables inherited from AI::FuzzyEngine::Variable:
218 142     142   265 sub _class_of_set { 'AI::FuzzyEngine::Set' }
219            
220             sub _curve_to_fun {
221             # Convert input format for membership functions
222             # to internal representation:
223             # [$x11, $y11, $x12, $y12, ... ]
224             # --> [ $x11, $x12, ... ] => [$y11, $y12, ... ] ]
225 136     136   2739 my ($class, $curve) = @_;
226 136         413 my %points = @$curve;
227 136         658 my @x = sort {$a<=>$b} keys %points;
  51         141  
228 136         320 my @y = @points{ @x };
229 136         485 return [ \@x, \@y ];
230             }
231            
232            
233            
234             1;
235            
236             =pod
237            
238             =head1 NAME
239            
240             AI::FuzzyEngine::Variable - Class used by AI::FuzzyEngine.
241            
242             =head1 DESCRIPTION
243            
244             Please see L for a description.
245            
246             =head1 SUPPORT
247            
248             You can find documentation for this module with the perldoc command.
249            
250             perldoc AI::FuzzyEngine
251            
252             =head1 AUTHOR
253            
254             Juergen Mueck, jmueck@cpan.org
255            
256             =head1 COPYRIGHT
257            
258             Copyright (c) Juergen Mueck 2013. All rights reserved.
259            
260             This library is free software; you can redistribute it and/or
261             modify it under the same terms as Perl itself.
262            
263             =cut
264