AI-FuzzyEngine

 view release on metacpan or  search on metacpan

Build.PL  view on Meta::CPAN

use 5.006;
use strict;
use warnings;
use Module::Build;

my $builder = Module::Build->new(
    module_name         => 'AI::FuzzyEngine',
    license             => 'perl',
    dist_author         => q{Juergen Mueck <jmueck@cpan.org>},
    dist_version_from   => 'lib/AI/FuzzyEngine.pm',
    build_requires => {
        'Test::More'      => 0,
        'Test::Most'      => 0,
        'List::Util'      => 0,
        'List::MoreUtils' => 0,
        'Scalar::Util'    => 0,
        'Carp'            => 0,
    },
    requires => {
        'perl' => 5.008009,
    },
    add_to_cleanup      => [ 'AI-FuzzyEngine-*' ],
    create_makefile_pl => 'traditional',
);
#  <img src="Changes" alt="Changes, 119B" class="" title="Changes" height="0" width="0" />
#  <img src="MANIFEST" alt="MANIFEST, 194B" class="" title="MANIFEST" height="0" width="0" />
#  <img src="README" alt="README, 1.6kB" class="" title="README" height="0" width="0" />
                                             
$builder->create_build_script();

Changes  view on Meta::CPAN


0.1.0   2013-02-08
        First version, released on an unsuspecting world.

0.1.1   2013-02-08
        README costumized. POD small improvement. 

0.2.0   2013-02-13
        PDL awareness
        Tests re-factored
        requires perl 5.8.9
        POD improved, PDL sections added

0.2.1   2013-02-14
        Bug in test fixed (use PDL removed)
        versioning of Set and Variable adapted

0.2.2   2013-02-27
        Declaring a version with version->declare() instead of qw()
        A variable can change its sets' membership functions 
        (e.g. within optimisation routines)

META.json  view on Meta::CPAN

   "license" : [
      "perl_5"
   ],
   "meta-spec" : {
      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
      "version" : "2"
   },
   "name" : "AI-FuzzyEngine",
   "prereqs" : {
      "build" : {
         "requires" : {
            "Carp" : 0,
            "List::MoreUtils" : 0,
            "List::Util" : 0,
            "Scalar::Util" : 0,
            "Test::More" : 0,
            "Test::Most" : 0
         }
      },
      "configure" : {
         "requires" : {
            "Module::Build" : "0.38"
         }
      },
      "runtime" : {
         "requires" : {
            "perl" : "5.008009"
         }
      }
   },
   "provides" : {
      "AI::FuzzyEngine" : {
         "file" : "lib/AI/FuzzyEngine.pm",
         "version" : "v0.2.2"
      },
      "AI::FuzzyEngine::Set" : {
         "file" : "lib/AI/FuzzyEngine/Set.pm",
         "version" : "v0.2.2"
      },
      "AI::FuzzyEngine::Variable" : {
         "file" : "lib/AI/FuzzyEngine/Variable.pm",
         "version" : "v0.2.2"
      }
   },
   "release_status" : "stable",
   "resources" : {
      "license" : [
         "http://dev.perl.org/licenses/"
      ]
   },
   "version" : "v0.2.2"
}

META.yml  view on Meta::CPAN

---
abstract: 'A Fuzzy Engine, PDL aware'
author:
  - 'Juergen Mueck <jmueck@cpan.org>'
build_requires:
  Carp: 0
  List::MoreUtils: 0
  List::Util: 0
  Scalar::Util: 0
  Test::More: 0
  Test::Most: 0
configure_requires:
  Module::Build: 0.38
dynamic_config: 1
generated_by: 'Module::Build version 0.38, CPAN::Meta::Converter version 2.112150'
license: perl
meta-spec:
  url: http://module-build.sourceforge.net/META-spec-v1.4.html
  version: 1.4
name: AI-FuzzyEngine
provides:
  AI::FuzzyEngine:
    file: lib/AI/FuzzyEngine.pm
    version: v0.2.2
  AI::FuzzyEngine::Set:
    file: lib/AI/FuzzyEngine/Set.pm
    version: v0.2.2
  AI::FuzzyEngine::Variable:
    file: lib/AI/FuzzyEngine/Variable.pm
    version: v0.2.2
requires:
  perl: 5.008009
resources:
  license: http://dev.perl.org/licenses/
version: v0.2.2

README  view on Meta::CPAN

AI-FuzzyEngine v0.2.2

This module is yet another implementation of a fuzzy inference system.
The aim was to  be able to code rules (no string parsing),
but avoid operator overloading,
and make it possible to split calculation into multiple steps.
All intermediate results (memberships of sets of variables)
should be available.

Beginning with v0.2.0 it is PDL aware,
meaning that it can handle piddles (PDL objects)
when running the fuzzy operations.
More information on PDL can be found at L<http://pdl.perl.org/>. 

Credits to Ala Qumsieh and his L<AI::FuzzyInference>,
that showed me that fuzzy is no magic.
I learned a lot by analyzing his code,

lib/AI/FuzzyEngine.pm  view on Meta::CPAN

sub new_variable {
    my ($self, @pars) = @_;

    my $variable_class = $self->_class_of_variable();
    my $var = $variable_class->new($self, @pars);
    push @{$self->{_variables}}, $var;
    Scalar::Util::weaken $self->{_variables}->[-1];
    return $var;
}

sub reset {
    my ($self) = @_;
    $_->reset() for $self->variables(); 
    return $self;
}

sub _class_of_variable { 'AI::FuzzyEngine::Variable' }

sub _non_is_a_piddle {
    return List::MoreUtils::none {ref $_ eq 'PDL'} @_;
}

my $_PDL_is_imported;

lib/AI/FuzzyEngine.pm  view on Meta::CPAN

                        crit  => [],
                        over  => [],
                   );
    # But output variables need membership functions for their sets:
    my $green = $fe->new_variable( -5 => 5,
                        decrease => [-5, 1, -2, 1, 0, 0            ],
                        ok       => [       -2, 0  0, 1, 2, 0      ],
                        increase => [              0, 0, 2, 1, 5, 1],
                   );

    # Reset FuzzyEngine (resets all variables)
    $fe->reset();

    # Reset a fuzzy variable directly
    $flow->reset;

    # Membership functions can be changed via the set's variable.
    # This might be useful during parameter identification algorithms
    # Changing a function resets the respective variable.
    $flow->change_set( med => [500, 0, 1000, 1, 1500, 0] );

    # Fuzzification of input variables
    $flow->fuzzify( 600 );
    $cap->fuzzify( 1000 );

    # Membership degrees of the respective sets are now available:
    my $flow_is_small = $flow->small(); # 0.8
    my $flow_is_med   = $flow->med();   # 0.2
    my $flow_is_huge  = $flow->huge();  # 0.0

    # RULES and their application

    # a) If necessary, calculate some internal variables first. 
    # They will not be defuzzified (in fact, $saturation can't)
    # Implicit application of 'and'
    # Multiple calls to a membership function

lib/AI/FuzzyEngine.pm  view on Meta::CPAN

                       $flow->huge(),
                       $cap->high(),
                 );
    $saturation->over( $flow->huge(), $fe->not( $cap->high() ) );

    # b) deduce output variable(s) (here: from internal variable $saturation)
    $green->decrease( $saturation->low()  );
    $green->ok(       $saturation->crit() );
    $green->increase( $saturation->over() );

    # All sets provide their respective membership degrees: 
    my $saturation_is_over = $saturation->over(); # This is no defuzzification!
    my $green_is_ok        = $green->ok();

    # Defuzzification ( is a matter of the fuzzy variable )
    my $delta_green = $green->defuzzify(); # -5 ... 5

=head2 Using PDL and its threading capability

    use PDL;
    use AI::FuzzyEngine;

    # (Probably a stupide example)
    my $fe        = AI::FuzzyEngine->new();

    # Declare variables as usual
    my $severity  = $fe->new_variable( 0 => 10,
                          low  => [0, 1, 3, 1, 5, 0       ],
                          high => [      3, 0, 5, 1, 10, 1],
                        );

    my $threshold = $fe->new_variable( 0 => 1,
                           low  => [0, 1, 0.2, 1, 0.8, 0,     ],
                           high => [      0.2, 0, 0.8, 1, 1, 1],
                         );
    
    my $problem   = $fe->new_variable( -0.5 => 2,
                           no  => [-0.5, 0, 0, 1, 0.5, 0, 1, 0],
                           yes => [         0, 0, 0.5, 1, 1, 1, 1.5, 1, 2, 0],
                         );

    # Input data is a pdl of arbitrary dimension

lib/AI/FuzzyEngine.pm  view on Meta::CPAN

    # Membership degrees are piddles now:
    print 'Severity is high: ', $severity->high, "\n";
    # [0 0.5 1 1]

    # Other variables might be piddles of other dimensions,
    # but all variables must be expandible to a common 'wrapping' piddle
    # ( in this case a 4x2 matrix with 4 colums and 2 rows)
    my $level = pdl( [0.6],
                     [0.2],
                   );
    $threshold->fuzzify( $level );

    print 'Threshold is low: ', $threshold->low(), "\n";
    # [
    #  [0.33333333]
    #  [         1]
    # ]

    # Apply some rules
    $problem->yes( $severity->high,  $threshold->low );
    $problem->no( $fe->not( $problem->yes )  );

    # Fuzzy results are represented by the membership degrees of sets 
    print 'Problem yes: ', $problem->yes,  "\n";
    # [
    #  [         0 0.33333333 0.33333333 0.33333333]
    #  [         0        0.5          1          1]
    # ]

    # Defuzzify the output variables
    # Caveat: This includes some non-threadable operations up to now
    my $problem_ratings = $problem->defuzzify();
    print 'Problems rated: ', $problem_ratings;

lib/AI/FuzzyEngine.pm  view on Meta::CPAN

=head1 EXPORT

Nothing is exported or exportable.

=head1 DESCRIPTION

This module is yet another implementation of a fuzzy inference system.
The aim was to  be able to code rules (no string parsing),
but avoid operator overloading,
and make it possible to split calculation into multiple steps.
All intermediate results (memberships of sets of variables)
should be available.

Beginning with v0.2.0 it is PDL aware,
meaning that it can handle piddles (PDL objects)
when running the fuzzy operations.
More information on PDL can be found at L<http://pdl.perl.org/>. 

Credits to Ala Qumsieh and his L<AI::FuzzyInference>,
that showed me that fuzzy is no magic.
I learned a lot by analyzing his code,

lib/AI/FuzzyEngine.pm  view on Meta::CPAN


Once built, the engine can create fuzzy variables by C<new_variable>:

    my $var = $fe->new_variable( $from => $to,
                        $name_of_set1 => [$x11, $y11, $x12, $y12, ... ],
                        $name_of_set2 => [$x21, $y21, $x22, $y22, ... ],
                        ...
                   );

Result is an L<AI::FuzzyEngine::Variable>.
The name_of_set strings are taken to assign corresponding methods
for the respective fuzzy variables.
They must be valid function identifiers.
Same name_of_set can used for different variables without conflict.
Take care:
There is no check for conflicts with predefined class methods. 

Fuzzy variables provide a method to fuzzify input values:

    $var->fuzzify( $val );

according to the defined sets and their membership functions.

The memberships of the sets of C<$var> are accessible
by the respective functions:

    my $membership_degree = $var->$name_of_set();

Membership degrees can be assigned directly (within rules for example):

    $var->$name_of_set( $membership_degree );

If multiple membership_degrees are given, they are "anded":

    $var->$name_of_set( $degree1, $degree2, ... ); # "and"

By this, simple rules can be coded directly:

    my $var_3->zzz( $var_1->xxx, $var_2->yyy, ... ); # "and"

this implements the fuzzy implication

    if $var_1->xxx and $var_2->yyy and ... then $var_3->zzz

The membership degrees of a variable's sets can be reset to undef:

    $var->reset(); # resets a variable
    $fe->reset();  # resets all variables

The fuzzy engine C<$fe> has all variables registered
that have been created by its C<new_variable> method.

A variable can be defuzzified:

    my $out_value = $var->defuzzify();

Membership functions can be replaced via a set's variable:

    $var->change_set( $name_of_set => [$x11n, $y11n, $x12n, $y12n, ... ] );

The variable will be reset when replacing a membership function
of any of its sets.
Interdependencies with other variables are not checked
(it might happen that the results of any rules are no longer valid,
so it needs some recalculations).

Sometimes internal variables are used that need neither fuzzification
nor defuzzification.
They can be created by a simplified call to C<new_variable>:

    my $var_int = $fe->new_variable( $name_of_set1 => [],
                                     $name_of_set2 => [],
                                     ...
                       );

lib/AI/FuzzyEngine.pm  view on Meta::CPAN

    my $negated     = $fe->not( $var1->zzz );

There is no magic.

A sequence of rules for the same set can be implemented as follows: 

    $var_3->zzz( $var_1->xxx, $var_2->yyy, ... );
    $var_3->zzz( $var_4->aaa, $var_5->bbb, ... );

The subsequent application of C<< $var_3->zzz(...) >>
corresponds to "or" operations (aggregation of rules).

Only a reset can reset C<$var_3>. 

=head2 PDL awareness

Membership degrees of sets might be either scalars or piddles now.

    $var_a->memb_fun_a(        5  ); # degree of memb_fun_a is a scalar
    $var_a->memb_fun_b( pdl(7, 8) ); # degree of memb_fun_b is a piddle

Empty piddles are not allowed, behaviour with bad values is not tested.

lib/AI/FuzzyEngine.pm  view on Meta::CPAN


This is my first module.
I'm happy about feedback that helps me to learn
and improve my contributions to the Perl ecosystem.

Please report any bugs or feature requests to
C<bug-ai-fuzzyengine at rt.cpan.org>, or through
the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AI-FuzzyEngine>.
I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc AI::FuzzyEngine

=head1 AUTHOR

Juergen Mueck, jmueck@cpan.org

=head1 COPYRIGHT

Copyright (c) Juergen Mueck 2013.  All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=cut

lib/AI/FuzzyEngine/Set.pm  view on Meta::CPAN

sub fuzzify {
    my ($self, $val) = @_;

    my $fun = $self->memb_fun;
    croak "No valid membership function"
        unless @{$fun->[0]}; # at least one x

    return $self->{degree} = $self->_interpol( $fun => $val );
}

sub reset {
    my ($self) = @_;
    $self->{degree} = 0;
    $self;
}

# Replace a membership function
# To be called by variable->change_set( 'setname' => $new_fun );
sub replace_memb_fun {
    my ($self, $new_fun) = @_;
    $self->{memb_fun} = $new_fun;

lib/AI/FuzzyEngine/Set.pm  view on Meta::CPAN

You can find documentation for this module with the perldoc command.

    perldoc AI::FuzzyEngine

=head1 AUTHOR

Juergen Mueck, jmueck@cpan.org

=head1 COPYRIGHT

Copyright (c) Juergen Mueck 2013.  All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=cut

lib/AI/FuzzyEngine/Variable.pm  view on Meta::CPAN

        my $c       = $set_class->centroid( $fun_agg );
        return $c;
    };

    # Need a function of my FuzzyEngine
    my $fe = $self->fuzzyEngine;
    die 'Internal: fuzzy_engine is lost' unless $fe;

    # Unify dimensions of all @degrees (at least one is a pdl)
    my @synched_degrees = $fe->_cat_array_of_piddles(@degrees)->dog;
    my @dims_to_reshape = $synched_degrees[0]->dims;

    # Make degrees flat to proceed them as lists
    my @flat_degrees    = map {$_->flat} @synched_degrees;
    my $flat_degrees    = PDL::cat( @flat_degrees );

    # Proceed degrees of @sets as synchronized lists
    my @degrees_per_el  = $flat_degrees->transpose->dog;
    my @defuzzified;
    for my $ix (reverse 0..$#degrees_per_el) {
        my $el_degrees = $degrees_per_el[$ix];
        # The next two lines cost much (75% of defuzzify)
        my $funs       = _clipped_funs( \@funs, [$el_degrees->list] );
        my $fun_agg    = $set_class->max_of_funs( @$funs );

        my $c          = $set_class->centroid( $fun_agg );
        $defuzzified[$ix] = $c;
    };

    # Build result in shape of unified membership degrees
    my $flat_defuzzified = PDL->pdl( @defuzzified );
    my $defuzzified      = $flat_defuzzified->reshape(@dims_to_reshape);
    return $defuzzified;
}

sub _clipped_funs {
    # Clip all membership functions of a variable
    # according to the respective membership degree (array of scalar)
    my ($funs, $degrees) = @_;
    my @funs    = @$funs;    # Dereferencing here saves some time
    my @degrees = @$degrees;
    my @clipped = List::MoreUtils::pairwise {
                     $set_class->clip_fun($a => $b)
                  } @funs, @degrees;
    return \@clipped;
}

sub reset {
    my ($self) = @_;
    $_->reset() for values %{$self->sets};
    return $self;
}

sub change_set {
    my ($self, $setname, $new_memb_fun) = @_;
    my $set = $self->set( $setname );

    # Some checks
    croak "Set $setname does not exist" unless defined $set;
    croak 'Variable is internal' if $self->is_internal;

    # Convert to internal representation
    my $fun = $self->_curve_to_fun( $new_memb_fun );

    # clip membership function to borders
    $set->set_x_limits( $fun, $self->from => $self->to );

    # Hand the new function over to the set
    $set->replace_memb_fun( $fun );

    # and reset the variable
    $self->reset;
    return;
}

sub _init {
    my ($self, @pars) = @_;

    croak "Too few arguments" unless @pars >= 2;

    # Test for internal variable
    my ($from, $to, @sets);

lib/AI/FuzzyEngine/Variable.pm  view on Meta::CPAN


sub _non_is_a_piddle {
    return List::MoreUtils::none {ref $_ eq 'PDL'} @_;
}

# Might change for Variables inherited from AI::FuzzyEngine::Variable:
sub _class_of_set { 'AI::FuzzyEngine::Set' }

sub _curve_to_fun {
    # Convert input format for membership functions
    # to internal representation:
    # [$x11, $y11, $x12, $y12, ... ]
    # --> [ $x11, $x12,  ... ] => [$y11, $y12, ... ] ]
    my ($class, $curve) = @_;
    my %points = @$curve;
    my @x      = sort {$a<=>$b} keys %points;
    my @y      = @points{ @x };
    return [ \@x, \@y ];
}


lib/AI/FuzzyEngine/Variable.pm  view on Meta::CPAN

You can find documentation for this module with the perldoc command.

    perldoc AI::FuzzyEngine

=head1 AUTHOR

Juergen Mueck, jmueck@cpan.org

=head1 COPYRIGHT

Copyright (c) Juergen Mueck 2013.  All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=cut

t/01-fuzzyEngine.t  view on Meta::CPAN

    local $set_pars{memb_fun} = [ [0.2, 0.3, 0.8, 1.0], # x
                                  [0.1, 0.5, 0.5, 0.0], # y
                                ];
    $s = $set_class->new(%set_pars);

    # fuzzify some values
    my @vals     = (  0, 0.2, 0.25, 0.3, 0.5, 0.8, 0.90, 1);
    my @expected = (0.1, 0.1, 0.30, 0.5, 0.5, 0.5, 0.25, 0 );
    my @got      = map { $s->fuzzify($_) } @vals;
    is_deeply( \@got, \@expected,
               'fuzzify incl. corner cases and reset of degree',
             );

    my $degree = $s->fuzzify( 0.2 );
    is( $degree, 0.1, 'fuzzify returns degree' );

    $set_pars{memb_fun} = [ [0, 1, 1, 2] => [1, 2, 3, 4] ];
    throws_ok {$s = AI::FuzzyEngine::Set->new(%set_pars)
              } qr/no double/i, 'Checks double interpolation coordinates';
};

t/01-fuzzyEngine.t  view on Meta::CPAN

    $v->fuzzify( 5 ); # $v->low > 0 && $v->high > 0

    my $new_memb_fun = [2, 1, 8, 0];
    $v->change_set( low => $new_memb_fun );

    is_deeply( $v->sets->{low}->memb_fun(),
               [ [0, 2, 8, 10] => [1, 1, 0, 0] ],
               'change_set works and adapts borders in x',
             );

    is_deeply( [$v->low, $v->high], [0, 0], 'change_set resets the variable' );

    throws_ok { $v->change_set( 'wrong_set' )
              } qr/set/i, 'change_set checks correct set name';

    1;
};

subtest "$var_class fuzzification and defuzzification" => sub {

    my $v  = $var_class->new( $fe,

t/01-fuzzyEngine.t  view on Meta::CPAN


    $v->fuzzify( 0 );
    is_deeply( [$v->low, $v->med, $v->high],
               [      1,     0.5,     0.25],
               'fuzzify fuzzifies all sets',
             );

    $v->fuzzify( 10 );
    is_deeply( [$v->low, $v->med, $v->high],
               [      0,     0.5,     0.75],
               'fuzzify resets and fuzzifies all sets',
             );

    # Defuzzification
    $v = AI::FuzzyEngine::Variable
        ->new( $fe,
               0 => 2,
               low  => [0 => 1, 1 => 1, 1.00001 => 0, 2 => 0],
               high => [0 => 0, 1 => 0, 1.00001 => 1, 2 => 1],
             );

    $v->low(  1 ); # explicit control for next tests
    $v->high( 0 );
    my $val = sprintf "%.2f", $v->defuzzify();
    is( $val*1, 0.5, 'defuzzy low' );

    $v->reset;
    $v->low(  0 );
    $v->high( 0.5 );
    $val = sprintf "%.2f", $v->defuzzify();
    is( $val*1, 1.5, 'defuzzy high' );

    $v->low( 1 );
    $val = $v->defuzzify();
    ok( ($val > 0.5 && $val < 1), 'defuzzy low + 0.5*high' );
};

t/01-fuzzyEngine.t  view on Meta::CPAN

             );

    can_ok( $v, 'low' );

    my $degree = $v->low;
    is( $degree, 0, 'initial value for degree of low' );

    $degree = $v->low(0.2, 0.1);
    is( $degree, 0.1, 'and / or for degree of low work' );

    $v->reset;
    is( $v->low, 0, 'reset works' );

    # Throw errors!
    throws_ok { $v->fuzzify(0)
              } qr/internal/, 'Checks illegal fuzzify call';
    throws_ok { $v->defuzzify
              } qr/internal/, 'Checks illegal defuzzify call';
    throws_ok { $v->change_set( low => [[]=>[]] )
              } qr/internal/i, 'Blocks change_set';
};

t/01-fuzzyEngine.t  view on Meta::CPAN

               'Variable attributes and set names by new_variable',
             );

    my $w = $fe->new_variable( 0 => 1,
                               'low'  => [0, 1],
                               'high' => [1, 0],
                             );

    is_deeply( [ $fe->variables() ],
               [$v, $w],
               'Engine stores variables (should be weakened)',
             );

    $v->low( 0.1 );
    $w->low( 0.2 );

    my $v_resetted = $v->reset;
    isa_ok( $v_resetted,
            $var_class,
            'What variable->reset returns',
          ) or exit;
    is( $v->low, 0.0, 'Variable can be resetted'       );
    is( $w->low, 0.2, 'Other variables stay unchanged' );

    my $fe_resetted = $fe->reset();
    isa_ok( $fe_resetted,
            $class,
            'What fuzzyEngine->reset returns',
          );
    is( $w->low, 0.0, 'FuzzyEngine resets all variables' );
};

subtest 'synopsis' => sub {

    # Engine (or factory) provides fuzzy logical arithmetic
    my $fe = $class->new();

    # Disjunction:
    my $a = $fe->or ( 0.2, 0.5, 0.8, 0.7 ); # 0.8
    # Conjunction:

t/01-fuzzyEngine.t  view on Meta::CPAN

                        crit  => [],
                        over  => [],
                   );
    # But output variables need membership functions for their sets:
    my $green = $fe->new_variable( -5 => 5,
                        decrease => [-5, 1, -2, 1, 0, 0            ],
                        ok       => [       -2, 0, 0, 1, 2, 0      ],
                        increase => [              0, 0, 2, 1, 5, 1],
                   );

    # Reset FuzzyEngine (resets all variables)
    $fe->reset();

    # Reset a fuzzy variable directly
    $flow->reset;

    # Membership functions can be changed via the set's variable.
    # This might be useful during parameter identification algorithms
    # Changing a function resets the respective variable.
    $flow->change_set( med => [500, 0, 1000, 1, 1500, 0] );

    # Fuzzification of input variables
    $flow->fuzzify( 600 );
    $cap->fuzzify( 1000 );

    # Membership degrees of the respective sets are now available:
    my $flow_is_small = $flow->small(); # 0.8
    my $flow_is_med   = $flow->med();   # 0.2
    my $flow_is_huge  = $flow->huge();  # 0.0

    # RULES and their application

    # a) first step, result is $saturation, an intermediate set
    # implicit application of 'and'
    # Multiple calls to a membership function
    # are similar to 'or' operations:
    $saturation->low( $flow->small(), $cap->avg()  );
    $saturation->low( $flow->small(), $cap->high() );
    $saturation->low( $flow->med(),   $cap->high() );

    # Explicite 'or', 'and' or 'not' possible:
    $saturation->crit( $fe->or( $fe->and( $flow->med(),  $cap->avg()  ),
                                $fe->and( $flow->huge(), $cap->high() ),

t/01-fuzzyEngine.t  view on Meta::CPAN

                       $flow->huge(),
                       $cap->high(),
                 );
    $saturation->over( $flow->huge(), $fe->not( $cap->high() ) );

    # b) second step, deduce output variable from internal state of saturation
    $green->decrease( $saturation->low()  );
    $green->ok(       $saturation->crit() );
    $green->increase( $saturation->over() );

    # All sets provide the respective membership degrees of their variables: 
    my $saturation_is_over = $saturation->over(); # no defuzzification!
    my $green_is_ok        = $green->ok();

    # Defuzzification ( is a matter of the fuzzy set )
    my $delta_green = $green->defuzzify(); # -5 ... 5

    ok( 1, 'POD synopsis' );
};

subtest 'PDL may not be loaded' => sub {

t/01-fuzzyEngine.t  view on Meta::CPAN

    }
    else {
        ok( (not exists $INC{PDL}), 'Module does not load PDL' );
    };
};

done_testing();

sub a_variable {
    # Careful!
    # a_variable does not register its result into $fuzzyEngine.
    # ==> is missing in $fe->variables;
    #
    my ($fuzzyEngine, @pars) = @_;
    my $v = var_class()->new( $fuzzyEngine,
                              0 => 1,
                              'low'  => [0, 0],
                              'high' => [1, 1],
                              @pars,
                            );
    return $v;

t/02-fuzzyEngine-pdl_aware.t  view on Meta::CPAN

                  '_cat_array_of_piddles checks for empty piddles';

};

subtest "$class PDL operations" => sub {
    my $fe = $class->new();

    # Negation:
    my $c = $fe->not( 0.4 );
    ok( ref $c eq '', 'not scalar: scalar' );
    ok( $c == 0.6,    'not scalar: result' );

    $c = $fe->not( pdl( 0.4 ) );
    isa_ok( $c, 'PDL', 'not(PDL scalar)'         );
    ok( $c == 0.6,     'not(PDL scalar): result' );

    $c = $fe->not( pdl([0.4, 0.5], [0, 1]) );
    isa_ok( $c, 'PDL',                     'not(PDL 2elem)'         );
    ok_all( $c == pdl([0.6, 0.5], [1, 0]), 'not(PDL 2elem): result' );

    # And and or use _cat_array_of_piddles
    # to bring input to the same dimensions
    # And
    $c = $fe->and( 0.4, pdl( [0.5] ) );
    isa_ok( $c, 'PDL', 'and(scalar, PDL)'         );
    ok_all( $c == 0.4, 'and(scalar, PDL): result' );

    $c = $fe->and( 0.6, pdl( [0.5, 0.7] ) );
    isa_ok( $c, 'PDL',             'and(scalar, 2elem PDL)'         );
    ok_all( $c == pdl([0.5, 0.6]), 'and(scalar, 2elem PDL): result' );

    # Or
    $c = $fe->or( 0.4, pdl( [0.5] ) );
    isa_ok( $c, 'PDL', 'or(scalar, PDL)'         );
    ok_all( $c == 0.5, 'or(scalar, PDL): result' );

    $c = $fe->or( 0.6, pdl( [0.5, 0.7] ) );
    isa_ok( $c, 'PDL',             'or(scalar, 2elem PDL)'         );
    ok_all( $c == pdl([0.6, 0.7]), 'or(scalar, 2elem PDL): result' );
};

my $fe = a_fuzzyEngine();
my %set_pars = ( fuzzyEngine => $fe,
                 variable    => a_variable( $fe ),
                 name        => 'few',
                 memb_fun    => [[7, 8] => [0, 1]],
               );

subtest "$set_class PDL degree" => sub {

t/02-fuzzyEngine-pdl_aware.t  view on Meta::CPAN

    is( $s->degree, 0.2, 'degree can be set by assignment of a piddle' );
    isa_ok( $s->degree, 'PDL', '$s->degree' );

    $s->degree( 0.1 );
    is( $s->degree, 0.2, 'Disjunction of last and new degree (1)' );

    $s->degree( 0.3 );
    is( $s->degree, 0.3, 'Disjunction of last and new degree (2)' );
    isa_ok( $s->degree, 'PDL', '$s->degree after recalculation' );

    $s->reset();
    is( ref $s->degree, '', 'reset makes degree a scalar again' );

    $s->degree( 0.3, pdl([0.5, 0.2]) );
    ok_all( $s->degree == pdl([0.3, 0.2] ),
            'Conjunction of multiple inputs ("and" operation)',
          );

    local $set_pars{memb_fun} = pdl( [[7, 8] => [0, 1]] );
    throws_ok{ $set_class->new(%set_pars)
             } qr/array ref/, 'Checks pureness of membership function';

t/02-fuzzyEngine-pdl_aware.t  view on Meta::CPAN

             );

    $v->low(  pdl(1, 0, 1) );
    $v->high( 0.5 ); # non pdl

    my $val = $v->defuzzify;
    isa_ok( $val, 'PDL', 'What $v->defuzzify returns from scalar+pdl' );
    my @size = $val->dims;
    is_deeply( \@size, [3], 'dimensions' );

    $v->reset;
    $v->low(  pdl(1, 0, 1) );
    $v->high( pdl(0, 0.5, 0.5) );
    my $val_got = $v->defuzzify;
    my $val_exp = pdl( 0.5, 1.5, 0.83 );
    ok_all( abs($val_got-$val_exp) < 0.1, 'defuzzify a piddle' );

    # Performance: Run testfile by nytprofiler
    $v->reset;
    my $n =100;
    $v->low(  random($n) );
    $v->high( 1-$v->low  );
    lives_ok { $val_got = $v->defuzzify; } "Defuzzifying $n elements";
};

subtest 'PDL synopsis' => sub {
#    use PDL;
#    use AI::FuzzyEngine;

    # (Probably a stupide example)
    my $fe       = AI::FuzzyEngine->new();

    # Declare variables as usual
    my $severity  = $fe->new_variable( 0 => 10,
                          low  => [0, 1, 3, 1, 5, 0       ],
                          high => [      3, 0, 5, 1, 10, 1],
                        );

    my $threshold = $fe->new_variable( 0 => 1,
                           low  => [0, 1, 0.2, 1, 0.8, 0,     ],
                           high => [      0.2, 0, 0.8, 1, 1, 1],
                         );
    my $problem   = $fe->new_variable( -0.5 => 2,
                           no  => [-0.5, 0, 0, 1, 0.5, 0, 1, 0],
                           yes => [         0, 0, 0.5, 1, 1, 1, 1.5, 1, 2, 0],
                         );

    # Input data is a pdl of arbitrary dimension
    my $data = pdl( [0, 4, 6, 10] );

t/02-fuzzyEngine-pdl_aware.t  view on Meta::CPAN

    # Membership degrees are piddles now: 
#    print 'Severity is high: ', $severity->high, "\n";
    # [0 0.5 1 1]

    # Other variables might be a piddle of other dimensions,
    # but variables must be extensible to a common 'wrapping' piddle
    # ( in this case a 4x2 matrix with 4 colums and 2 rows)
    my $level = pdl( [0.6],
                     [0.2],
                   );
    $threshold->fuzzify( $level );

#    print 'Threshold is low: ', $threshold->low(), "\n";
    # [
    #  [0.33333333]
    #  [         1]
    # ]

    # Apply the rule base
    # --> no for loops, no explicit expansion, ...
    $problem->yes( $severity->high,  $threshold->low );
    $problem->no( $fe->not( $problem->yes )  );

#    print 'Problem yes: ', $problem->yes,  "\n";
    # [
    #  [         0 0.33333333 0.33333333 0.33333333]
    #  [         0        0.5          1          1]
    # ]

    # Defuzzify the output variables
    # Caveat: This includes some non-threadable operations up to now

t/02-fuzzyEngine-pdl_aware.t  view on Meta::CPAN

done_testing();

sub ok_all {
    my ($p, $descr) = @_;
    die 'First arg must be a piddle' unless ref $p eq 'PDL';
    ok(  $p->all() , $descr || '' );
}

sub a_variable {
    # Careful!
    # a_variable does not register its result into $fuzzyEngine.
    # ==> is missing in $fe->variables;
    #
    my ($fuzzyEngine, @pars) = @_;
    my $v = var_class()->new( $fuzzyEngine,
                              0 => 1,
                              'low'  => [0, 0],
                              'high' => [1, 1],
                              @pars,
                            );
    return $v;



( run in 1.343 second using v1.01-cache-2.11-cpan-49f99fa48dc )