diff --git a/Changes b/Changes index a7b1228..5a02315 100644 --- a/Changes +++ b/Changes @@ -20,3 +20,6 @@ Revision history for Math-Function-Interpolator 0.07 19/06/2014 Makefile improved, added required modules + +0.08 23/06/2014 + Removed Moose, Moo uses and made pure old style object oriented diff --git a/Makefile.PL b/Makefile.PL index c6441ac..e7a4b7a 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -18,9 +18,6 @@ WriteMakefile( 'Test::More' => 0, 'Test::FailWarnings' => 0, 'Test::Exception' => 0, - 'Moo' => 0, - 'Moo::Role' => 0, - 'MooX::Traits' => 0, 'Module::Runtime' => 0, 'Module::Pluggable' => 0, 'Carp' => 0, @@ -32,6 +29,15 @@ WriteMakefile( 'Number::Closest::XS' => 0, 'Scalar::Util' => 0, }, + (! eval { ExtUtils::MakeMaker->VERSION(6.46) } ? () : + (META_ADD => { + resources => { + homepage => 'https://github.com/binary-com/perl-Math-Function-Interpolator', + repository => 'git@github.com:binary-com/perl-Math-Function-Interpolator.git', + bugtracker => 'http://rt.cpan.org/NoAuth/Bugs.html?Dist=Math-Function-Interpolator', + }, + }) + ), PREREQ_PM => { }, dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, diff --git a/lib/Math/Function/Interpolator.pm b/lib/Math/Function/Interpolator.pm index b8a28bc..e4742cf 100644 --- a/lib/Math/Function/Interpolator.pm +++ b/lib/Math/Function/Interpolator.pm @@ -4,9 +4,6 @@ use 5.006; use strict; use warnings FATAL => 'all'; -use Moo; -with qw(MooX::Traits); - use Carp qw(confess); use Scalar::Util qw(looks_like_number); @@ -14,19 +11,13 @@ use Number::Closest::XS qw(find_closest_numbers_around); use List::MoreUtils qw(pairwise indexes); use List::Util qw(min max); -use Module::Runtime; -use Module::Pluggable - sub_name => 'interpolate_methods', - search_path => ['Math::Function::Interpolator'], -; - =head1 NAME Math::Function::Interpolator - Interpolation made easy =head1 VERSION -Version 0.07 +Version 0.08 =head1 SYNOPSIS @@ -54,51 +45,72 @@ HashRef of points for interpolations =cut -our $VERSION = '0.07'; - -# Automatically load all interpolate methods -has 'interpolate_classes' => ( - is => 'ro', - lazy => 1, - default => sub { - my ($self) = @_; - my @modules = $self->interpolate_methods(); - foreach my $module (@modules) { - Module::Runtime::require_module($module); - } - return 1; - } -); - -has points => ( - is => 'ro', - isa => sub { - die "Points $_[0] shold be hash" unless ref $_[0] eq 'HASH'; - }, - required => 1, -); +our $VERSION = '0.08'; =head1 METHODS -=head2 BUILDARGS +=head2 new -BUILDARGS +New instance method =cut -sub BUILDARGS { ## no critic (Subroutines::RequireArgUnpacking) - my $self = shift; - my %args = ref( $_[0] ) ? %{ $_[0] } : @_; +sub new { + my $class = shift; + my %params_ref = ref( $_[0] ) ? %{ $_[0] } : @_; + + confess "points are required to do interpolation" + unless $params_ref{'points'}; # We can't interpolate properly on undef values so make sure we know # they are missing by removing them entirely. - my $points = $args{points}; - $args{points} = { + my $points = $params_ref{points}; + $params_ref{points} = { map { $_ => $points->{$_} } grep { defined $points->{$_} } keys %$points }; + + my $self = { + _points => $params_ref{'points'}, + _classes_loaded => 0 + }; + my $obj = bless $self, $class; + + if ( $class =~/Interpolator$/i ){ + # Load all interpolator classes when only made object from main + # Interpolator classes + $obj->_load_interpolator_classes(); + } + + return $obj; +} + +sub _load_interpolator_classes { + my ( $self ) = @_; + + use Module::Runtime; + use Module::Pluggable + sub_name => 'interpolate_methods', + search_path => ['Math::Function::Interpolator'], + ; + + my @modules = $self->interpolate_methods(); + foreach my $module (@modules) { + Module::Runtime::require_module($module); + } + $self->{'_classes_loaded'} = 1; + return 1; +} + +=head2 points + +points + +=cut - return \%args; +sub points { + my ( $self ) = @_; + return $self->{'_points'}; } =head2 linear @@ -109,11 +121,9 @@ This method do the linear interpolation. It solves for point_y linearly given po sub linear { my ( $self, $x ) = @_; - confess "point_x must be numeric" if !looks_like_number($x); - $self->interpolate_classes(); - return Math::Function::Interpolator->with_traits( - 'Math::Function::Interpolator::Linear')->new( interpolate => $self ) - ->do_calculation($x); + return Math::Function::Interpolator::Linear->new( + points => $self->points + )->linear( $x ); } =head2 quadratic @@ -124,11 +134,9 @@ This method do the quadratic interpolation. It solves the interpolated_y value g sub quadratic { my ( $self, $x ) = @_; - confess "point_x must be numeric" if !looks_like_number($x); - $self->interpolate_classes(); - return Math::Function::Interpolator->with_traits( - 'Math::Function::Interpolator::Quadratic')->new( interpolate => $self ) - ->do_calculation($x); + return Math::Function::Interpolator::Quadratic->new( + points => $self->points + )->quadratic( $x ); } =head2 cubic @@ -139,11 +147,9 @@ This method do the cubic interpolation. It solves the interpolated_y given point sub cubic { my ( $self, $x ) = @_; - confess "point_x must be numeric" if !looks_like_number($x); - $self->interpolate_classes(); - return Math::Function::Interpolator->with_traits( - 'Math::Function::Interpolator::Cubic')->new( interpolate => $self ) - ->do_calculation($x); + return Math::Function::Interpolator::Cubic->new( + points => $self->points + )->cubic( $x ); } =head2 closest_three_points diff --git a/lib/Math/Function/Interpolator/Cubic.pm b/lib/Math/Function/Interpolator/Cubic.pm index 1cc03dd..59c2b10 100644 --- a/lib/Math/Function/Interpolator/Cubic.pm +++ b/lib/Math/Function/Interpolator/Cubic.pm @@ -4,9 +4,9 @@ use 5.006; use strict; use warnings FATAL => 'all'; -our $VERSION = '0.02'; +our $VERSION = '0.03'; -use Moo::Role; +use parent 'Math::Function::Interpolator'; use Carp qw(confess); use List::MoreUtils qw(pairwise indexes); @@ -19,9 +19,9 @@ Math::Function::Interpolator::Cubic =head1 SYNOPSIS - use Math::Function::Interpolator; + use Math::Function::Interpolator::Cubic; - my $interpolator = Math::Function::Interpolator->new( + my $interpolator = Math::Function::Interpolator::Cubic->new( points => {1=>2,2=>3,3=>4} ); @@ -34,42 +34,21 @@ It solves the interpolated_y given point_x and a minimum of 5 data points. =head1 FIELDS -=head2 interpolate (REQUIRED) +=head2 points (REQUIRED) -Interpolations class object +HashRef of points for interpolations =cut -has 'interpolate' => ( - is => 'ro', - isa => sub { - die "Must be Interpolate class" - unless ref $_[0] eq 'Math::Function::Interpolator'; - }, - required => 1 -); - -has _sorted_Xs => ( - is => 'ro', - lazy => 1, - builder => '_build__sorted_Xs', -); - -sub _build__sorted_Xs { +sub _sorted_Xs { my ($self) = @_; - return [ sort { $a <=> $b } keys %{ $self->interpolate->points } ]; + return [ sort { $a <=> $b } keys %{ $self->points } ]; } -has _spline_points => ( - is => 'ro', - lazy => 1, - builder => '_build__spline_points', -); - -sub _build__spline_points { +sub _spline_points { my ($self) = @_; - my $points_ref = $self->interpolate->points; + my $points_ref = $self->points; my $Xs = $self->_sorted_Xs; my @Ys = map { $points_ref->{$_} } @$Xs; @@ -121,18 +100,18 @@ sub _extrapolate_spline { =head1 METHODS -=head2 do_calculation +=head2 cubic -do_calculation +cubic =cut # Returns the interpolated_y given point_x and a minimum of 5 data points -sub do_calculation { +sub cubic { my ( $self, $x ) = @_; - confess "sought[$x] must be numeric" if !looks_like_number($x); - my $ap = $self->interpolate->points; + confess "sought_point[$x] must be a numeric" if !looks_like_number($x); + my $ap = $self->points; return $ap->{$x} if defined $ap->{$x}; # No interpolation needed. my $Xs = $self->_sorted_Xs; diff --git a/lib/Math/Function/Interpolator/Linear.pm b/lib/Math/Function/Interpolator/Linear.pm index 0391486..caec316 100644 --- a/lib/Math/Function/Interpolator/Linear.pm +++ b/lib/Math/Function/Interpolator/Linear.pm @@ -4,9 +4,10 @@ use 5.006; use strict; use warnings FATAL => 'all'; -our $VERSION = '0.02'; +use parent 'Math::Function::Interpolator'; + +our $VERSION = '0.03'; -use Moo::Role; use Carp qw(confess); use Number::Closest::XS qw(find_closest_numbers_around); use Scalar::Util qw(looks_like_number); @@ -17,9 +18,9 @@ Math::Function::Interpolator::Linear - Interpolation made easy =head1 SYNOPSIS - use Math::Function::Interpolator; + use Math::Function::Interpolator::Linear; - my $interpolator = Math::Function::Interpolator->new( + my $interpolator = Math::Function::Interpolator::Linear->new( points => {1=>2,2=>3,3=>4} ); @@ -32,35 +33,26 @@ It solves for point_y linearly given point_x and an array of points. =head1 FIELDS -=head2 interpolate (REQUIRED) +=head2 points (REQUIRED) -Interpolations class object +HashRef of points for interpolations =cut -has 'interpolate' => ( - is => 'ro', - isa => sub { - die "Must be Interpolate class" - unless ref $_[0] eq 'Math::Function::Interpolator'; - }, - required => 1 -); - =head1 METHODS -=head2 do_calculation +=head2 linear -do_calculation +linear =cut # Solves for point_y linearly given point_x and an array of points. -sub do_calculation { +sub linear { my ( $self, $x ) = @_; confess "sought_point[$x] must be a number" unless looks_like_number($x); - my $ap = $self->interpolate->points; + my $ap = $self->points; return $ap->{$x} if defined $ap->{$x}; # no need to interpolate my @Xs = keys %$ap; diff --git a/lib/Math/Function/Interpolator/Quadratic.pm b/lib/Math/Function/Interpolator/Quadratic.pm index d65ade4..19469af 100644 --- a/lib/Math/Function/Interpolator/Quadratic.pm +++ b/lib/Math/Function/Interpolator/Quadratic.pm @@ -4,9 +4,9 @@ use 5.006; use strict; use warnings FATAL => 'all'; -our $VERSION = '0.03'; +our $VERSION = '0.04'; -use Moo::Role; +use parent 'Math::Function::Interpolator'; use Carp qw(confess); use Math::Cephes::Matrix qw(mat); @@ -19,9 +19,9 @@ Math::Function::Interpolator::Quadratic =head1 SYNOPSIS - use Math::Function::Interpolator; + use Math::Function::Interpolator::Quadratic; - my $interpolator = Math::Function::Interpolator->new( + my $interpolator = Math::Function::Interpolator::Quadratic->new( points => {1=>2,2=>3,3=>4,4=>5,5=>6} ); @@ -34,43 +34,31 @@ It solves the interpolated_y given point_x and a minimum of 5 data points. =head1 FIELDS -=head2 interpolate (REQUIRED) - -Interpolations class object - -=cut - -has 'interpolate' => ( - is => 'ro', - isa => sub { - die "Must be Interpolate class" - unless ref $_[0] eq 'Math::Function::Interpolator'; - }, - required => 1 -); +=head2 points (REQUIRED) +HashRef of points for interpolations =head1 METHODS -=head2 do_calculation +=head2 quadratic -do_calculation +quadratic =cut # Returns the interpolated_y value given point_x with 3 data points -sub do_calculation { +sub quadratic { my ( $self, $x ) = @_; confess "sought_point[$x] must be a number" unless looks_like_number($x); - my $ap = $self->interpolate->points; + my $ap = $self->points; return $ap->{$x} if defined $ap->{$x}; # no need to interpolate my @Xs = keys %$ap; confess "cannot interpolate with fewer than 3 data points" if scalar @Xs < 3; - my @points = $self->interpolate->closest_three_points( $x, \@Xs ); + my @points = $self->closest_three_points( $x, \@Xs ); # Three cofficient my $abc = mat( [ map { [ $_**2, $_, 1 ] } @points ] ); diff --git a/t/01-calculation.t b/t/01-calculation.t index 0a753f5..00f7e00 100644 --- a/t/01-calculation.t +++ b/t/01-calculation.t @@ -1,12 +1,20 @@ -use Test::More tests => 3; +use Test::More tests => 1; +use Test::FailWarnings; +use Test::Exception; + use Math::Function::Interpolator; -my $interpolator = Math::Function::Interpolator->new( - points => {1=>2,2=>3,3=>4,4=>5,5=>6} -); +subtest "general checks" => sub { + plan tests => 4; + throws_ok { Math::Function::Interpolator->new() } qr/points are required to do interpolation/, + "cannot create interpolator object without data point"; -is ( $interpolator->linear(1.5), 2.5, 'Linear Interpolation'); -is ( $interpolator->quadratic(2.3), 3.3, 'Quadratic Interpolation'); -is ( $interpolator->cubic(4.5), 5.5, 'Cubic Interpolation'); + my $interpolator = Math::Function::Interpolator->new( + points => {1=>2,2=>3,3=>4,4=>5,5=>6} + ); + is ( $interpolator->linear(1.5), 2.5, 'Linear Interpolation'); + is ( $interpolator->quadratic(2.3), 3.3, 'Quadratic Interpolation'); + is ( $interpolator->cubic(4.5), 5.5, 'Cubic Interpolation'); +}; diff --git a/t/02-intense_test.t b/t/02-intense_test.t index 0c183d2..bec37c2 100644 --- a/t/02-intense_test.t +++ b/t/02-intense_test.t @@ -3,10 +3,9 @@ use Test::FailWarnings; use Test::Exception; use Math::Function::Interpolator; - -my $interpolator = Math::Function::Interpolator->new( - points => {1=>2,2=>3,3=>4,4=>5,5=>6} -); +use Math::Function::Interpolator::Linear; +use Math::Function::Interpolator::Quadratic; +use Math::Function::Interpolator::Cubic; subtest "closest_three_points" => sub { plan tests => 9; @@ -29,16 +28,16 @@ subtest "closest_three_points" => sub { subtest "general checks for linear" => sub { plan tests => 4; my $o; - lives_ok { $o = Math::Function::Interpolator->new(points => {2 => 3, 4 => 5.5}) } "can instantiate object with 2 points"; + lives_ok { $o = Math::Function::Interpolator::Linear->new(points => {2 => 3, 4 => 5.5}) } "can instantiate object with 2 points"; lives_ok { $o->linear(2.4) } "can linear given an X"; - throws_ok { $o->linear('abc') } qr/point_x must be numeric/, "dies when X is not a number"; - throws_ok { Math::Function::Interpolator->new(points => {2 => 3,})->linear(1) } qr/cannot interpolate with fewer than 2 data points/, + throws_ok { $o->linear('abc') } qr/sought\S+ must be a number/, "dies when X is not a number"; + throws_ok { Math::Function::Interpolator::Linear->new(points => {2 => 3,})->linear(1) } qr/cannot interpolate with fewer than 2 data points/, "cannot linear linear equation with one data point"; }; subtest "verify linear interpolation and extrapolation" => sub { plan tests => 4; - my $o = Math::Function::Interpolator->new( + my $o = Math::Function::Interpolator::Linear->new( points => { 1 => 3, 2 => 4.5, @@ -53,10 +52,10 @@ subtest "verify linear interpolation and extrapolation" => sub { subtest "general checks for quadratic" => sub { plan tests => 4; my $o; - lives_ok { $o = Math::Function::Interpolator->new(points => {2 => 3, 4 => 5.5, 6 => 8}) } "can instantiate object with 3 points"; + lives_ok { $o = Math::Function::Interpolator::Quadratic->new(points => {2 => 3, 4 => 5.5, 6 => 8}) } "can instantiate object with 3 points"; lives_ok { $o->quadratic(2.4) } "can quadratic given an X"; - throws_ok { $o->quadratic('abc') } qr/point_x must be numeric/, "dies when X is not a number"; - throws_ok { Math::Function::Interpolator->new(points => {2 => 3, 4 => 5})->quadratic(1) } + throws_ok { $o->quadratic('abc') } qr/sought\S+ must be a number/, "dies when X is not a number"; + throws_ok { Math::Function::Interpolator::Quadratic->new(points => {2 => 3, 4 => 5})->quadratic(1) } qr/cannot interpolate with fewer than 3 data points/, "cannot solve quadratic equation with two data point"; }; @@ -68,20 +67,20 @@ subtest "verify quadratic interpolation and extrapolation" => sub { 2 => 3, -2 => 19, }; - my $o = Math::Function::Interpolator->new(points => $points); + my $o = Math::Function::Interpolator::Quadratic->new(points => $points); is($o->quadratic(2.4), 2.72, "correctly interpolate y given x=2.4"); $points->{4} = 7; - my $o_new = Math::Function::Interpolator->new(points => $points); + my $o_new = Math::Function::Interpolator::Quadratic->new(points => $points); lives_ok { $o_new->quadratic(2.4) } "can solve quadratic equation with 4 data points "; }; subtest "general checks for cubic" => sub { plan tests => 4; my $o; - lives_ok { $o = Math::Function::Interpolator->new(points => {2 => 3, 4 => 5.5, 6 => 8, 7 => 9, 10 => 14}) } "can instantiate object with 5 points"; + lives_ok { $o = Math::Function::Interpolator::Cubic->new(points => {2 => 3, 4 => 5.5, 6 => 8, 7 => 9, 10 => 14}) } "can instantiate object with 5 points"; lives_ok { $o->cubic(2.4) } "can cubic given an X"; - throws_ok { $o->cubic('abc') } qr/point_x must be numeric/, "dies when X is not a number"; - throws_ok { Math::Function::Interpolator->new(points => {2 => 3, 4 => 5, 2 => 6, 4 => 11})->cubic(1) } + throws_ok { $o->cubic('abc') } qr/sought\S+ must be a numeric/, "dies when X is not a number"; + throws_ok { Math::Function::Interpolator::Cubic->new(points => {2 => 3, 4 => 5, 2 => 6, 4 => 11})->cubic(1) } qr/cannot interpolate with fewer than 5 data points/, "cannot cubic equation with four data point"; }; @@ -106,7 +105,7 @@ subtest "cubic interpolation compared to Bloomberg" => sub { 104 => 0.2402 }; - my $o = Math::Function::Interpolator->new(points => $points); + my $o = Math::Function::Interpolator::Cubic->new(points => $points); foreach my $num (96 .. 104) { next if grep { $_ == $num } keys %$points; my $test = $o->cubic($num); @@ -124,7 +123,7 @@ subtest "cubic extrapolation" => sub { 102.5 => 0.2461, 105 => 0.2364 }; - my $o = Math::Function::Interpolator->new(points => $points); + my $o = Math::Function::Interpolator::Cubic->new(points => $points); lives_ok { $o->cubic(80) } "can extrapolate"; my $first = $o->cubic(94); my $second = $o->cubic(93);