File Coverage

blib/lib/PDL/Fit/Linfit.pm
Criterion Covered Total %
statement 39 39 100.0
branch 5 10 50.0
condition 1 3 33.3
subroutine 8 8 100.0
pod 0 1 0.0
total 53 61 86.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PDL::Fit::Linfit - routines for fitting data with linear combinations of functions.
4              
5             =head1 DESCRIPTION
6              
7             This module contains routines to perform general curve-fits to a set (linear combination)
8             of specified functions.
9              
10             Given a set of Data:
11              
12             (y0, y1, y2, y3, y4, y5, ...ynoPoints-1)
13              
14             The fit routine tries to model y as:
15              
16             y' = beta0*x0 + beta1*x1 + ... beta_noCoefs*x_noCoefs
17              
18             Where x0, x1, ... x_noCoefs, is a set of functions (curves) that
19             the are combined linearly using the beta coefs to yield an approximation
20             of the input data.
21              
22             The Sum-Sq error is reduced to a minimum in this curve fit.
23              
24             B
25              
26             =over 1
27              
28             =item $data
29              
30             This is your data you are trying to fit. Size=n
31              
32             =item $functions
33              
34             2D array. size (n, noCoefs). Row 0 is the evaluation
35             of function x0 at all the points in y. Row 1 is the evaluation of
36             of function x1 at all the points in y, ... etc.
37              
38             Example of $functions array Structure:
39              
40             $data is a set of 10 points that we are trying to model using
41             the linear combination of 3 functions.
42              
43             $functions = ( [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], # Constant Term
44             [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], # Linear Slope Term
45             [ 0, 2, 4, 9, 16, 25, 36, 49, 64, 81] # quadradic term
46             )
47              
48             =back
49              
50             =head1 SYNOPSIS
51              
52             $yfit = linfit1d $data, $funcs
53              
54              
55             =head1 FUNCTIONS
56              
57             =head2 linfit1d
58              
59             =for ref
60              
61             1D Fit linear combination of supplied functions to data using min chi^2 (least squares).
62              
63             =for usage
64              
65             Usage: ($yfit, [$coeffs]) = linfit1d [$xdata], $data, $fitFuncs, [Options...]
66              
67             =for sig
68              
69             Signature: (xdata(n); ydata(n); $fitFuncs(n,order); [o]yfit(n); [o]coeffs(order))
70              
71             Uses a standard matrix inversion method to do a least
72             squares/min chi^2 fit to data.
73              
74             Returns the fitted data and optionally the coefficients.
75              
76             One can broadcast over extra dimensions to do multiple fits (except
77             the order can not be broadcasted over - i.e. it must be one fixed
78             set of fit functions C.
79              
80             The data is normalised internally to avoid overflows (using the
81             mean of the abs value) which are common in large polynomial
82             series but the returned fit, coeffs are in
83             unnormalised units.
84              
85              
86             =for example
87              
88             # Generate data from a set of functions
89             $xvalues = sequence(100);
90             $data = 3*$xvalues + 2*cos($xvalues) + 3*sin($xvalues*2);
91            
92             # Make the fit Functions
93             $fitFuncs = cat $xvalues, cos($xvalues), sin($xvalues*2);
94            
95             # Now fit the data, Coefs should be the coefs in the linear combination
96             # above: 3,2,3
97             ($yfit, $coeffs) = linfit1d $data,$fitFuncs;
98            
99              
100             =for options
101              
102             Options:
103             Weights Weights to use in fit, e.g. 1/$sigma**2 (default=1)
104              
105              
106             =cut
107              
108             package PDL::Fit::Linfit;
109 1     1   119894 use strict;
  1         3  
  1         46  
110 1     1   32 use warnings;
  1         3  
  1         58  
111              
112 1     1   698 use PDL::Core;
  1         26261  
  1         6  
113 1     1   819 use PDL::Basic;
  1         7036  
  1         7  
114 1     1   220 use PDL::Exporter;
  1         2  
  1         2  
115 1     1   20 use PDL::Options ':Func';
  1         1  
  1         104  
116 1     1   586 use PDL::MatrixOps qw(inv);
  1         4376  
  1         7  
117              
118             our @EXPORT_OK = qw( linfit1d );
119             our %EXPORT_TAGS = (Func=>\@EXPORT_OK);
120             our @ISA = qw( PDL::Exporter );
121              
122             sub PDL::linfit1d {
123 2 50   2 0 326284 my $opthash = ref($_[-1]) eq "HASH" ? pop(@_) : {} ;
124 2         13 my %opt = parse( { Weights=>ones(1) }, $opthash ) ;
125 2 50 33     885 barf "Usage: linfit1d incorrect args\n" if $#_<1 or $#_ > 3;
126 2         10 my ($x, $y, $fitfuncs) = @_;
127 2 50       9 if ($#_ == 1) {
128 2         6 ($y, $fitfuncs) = @_;
129 2         12 $x = $y->xvals;
130             }
131 2         270 my $wt = $opt{Weights};
132             # Internally normalise data
133 2         36 my $ymean = (abs($y)->sum)/($y->nelem);
134 2 50       362 $ymean = 1 if $ymean == 0;
135 2         286 my $y2 = $y / $ymean;
136             # Do the fit
137 2         84 my $M = $fitfuncs->transpose;
138 2         35 my $C = $M->transpose x ($M * $wt->dummy(0)) ;
139 2         575 my $Y = $M->transpose x ($y2->dummy(0) * $wt->dummy(0));
140             # Fitted coefficients vector
141 2         376 my $c = inv($C) x $Y;
142             # Fitted data
143 2         10491 my $yfit = ($M x $c)->clump(2) * $ymean; # Remove first dim=1, un-normalise
144 2 50       154 return $yfit if !wantarray;
145 2         9 return ($yfit, $c->clump(2) * $ymean); # Un-normalise
146             }
147             *linfit1d = *linfit1d = \&PDL::linfit1d;
148              
149             1;