1

I have a dataset containing two vectors of points, X and Y that represents measurements of an "exponential-like" phenomenon (i.e. Y = A*exp(b*x)). When fitting it with an exponential equation I'm getting a nice-looking fit, but when I'm using it to compute things it turns out that the fit is not quite as accurate as I would hope.

Currently my most promising idea is a piecewise exponential fit (taking about 6 (x,y) pairs each time), which seems to provide better results in cases I tested manually. Here's a diagram to illustrate what I mean to do:

// Assuming a window of size WS=4:
 - - - - - - - - - - - -  //the entire X span of the data
[- - - -]- - // the fit that should be evaluated for X(1)<= x < X(floor(WS/2)+1)
 -[- - - -]- // the fit that should be evaluated for X(3)<= x < X(4)
...
 - - - - - -[- - - -]- - // X(8)<= x < X(9)
... //and so on

Some Considerations:

  • I considered filtering the data before fitting, but this is tricky since I don't really know anything about the type of noise it contains.
  • I would like the piecewise fit (including all different cases) to be accessible using a single function handle. It's very similar to MATLAB's Shape-preserving "PCHIP" interpolant, only that it should use an exponential function instead.
  • The creation of the fit does not need to happen during the runtime of another code. I even prefer to create it separately.
  • I'm not worried about the potential unsmoothness of the final function.
  • The only way of going about this I could think of is defining an .m file as explained in Fit a Curve Defined by a File, but that would require manually writing conditions for almost as many cases as I have points (or obviously write a code that generates this code for me, which is also a considerable effort).

Relevant code snippets:

clear variables; clc;
%% // Definitions
CONSTS.N_PARAMETERS_IN_MODEL = 2; %// For the model y = A*exp(B*x);
CONSTS.WINDOW_SIZE = 4; 
assert(~mod(CONSTS.WINDOW_SIZE,2) && CONSTS.WINDOW_SIZE>0,...
    'WINDOW_SIZE should be a natural even number.');
%% // Example Data
X = [0.002723682418630,0.002687088539567,0.002634005004610,0.002582978173834,...
     0.002530684550171,0.002462144527884,0.002397219225698,0.002341097974950,...
     0.002287544321171,0.002238889510803]';
Y = [0.178923076435990,0.213320004768074,0.263918364216839,0.324208349386613,...
     0.394340431220509,0.511466688684182,0.671285738221314,0.843849959919278,...
     1.070756756433334,1.292800046096531]';
assert(CONSTS.WINDOW_SIZE <= length(X),...
    'WINDOW_SIZE cannot be larger than the amount of points.');
X = flipud(X); Y = flipud(Y); % ascending sorting is needed later for histc
%% // Initialization
nFits = length(X)-CONSTS.WINDOW_SIZE+1;
coeffMat(nFits,CONSTS.N_PARAMETERS_IN_MODEL) = 0; %// Preallocation
ft = fittype( 'exp1' );
opts = fitoptions( 'Method', 'NonlinearLeastSquares' );
%% // Populate coefficient matrix
for ind1 = 1:nFits
    [xData, yData] = prepareCurveData(...
        X(ind1:ind1+CONSTS.WINDOW_SIZE-1),Y(ind1:ind1+CONSTS.WINDOW_SIZE-1));
    %// Fit model to data:
    fitresult = fit( xData, yData, ft, opts );
    %// Save coefficients:
    coeffMat(ind1,:) = coeffvalues(fitresult);
end
clear ft opts ind1 xData yData fitresult ans
%% // Get the transition points
xTrans = [-inf; X(CONSTS.WINDOW_SIZE/2+1:end-CONSTS.WINDOW_SIZE/2); inf];

At this point, xTrans and coeffMat contain all the required information to evaluate the fits. To show this we can look at a vector of some test data:

testPts = [X(1), X(1)/2, mean(X(4:5)), X(CONSTS.WINDOW_SIZE)*1.01,2*X(end)];

...and finally the following should roughly happen internally within the handle:

%% // Get the correct fit# to be evaluated:
if ~isempty(xTrans) %// The case of nFits==1
    rightFit = find(histc(testPts(3),xTrans));
else
    rightFit = 1;
end
%% // Actually evaluate the right fit
f = @(x,A,B)A*exp(B*x);
yy = f(testPts(3),coeffMat(rightFit,1),coeffMat(rightFit,2));

And so my problem is how to hold that last bit (along with all the fit coefficients) inside a single handle, that accepts an arbitrarily-sized input of points to interpolate on?

Related resources:

Community
  • 1
  • 1
Dev-iL
  • 23,742
  • 7
  • 57
  • 99

2 Answers2

2

It's not all clear but why not to puts things into a class ?

classdef Piecewise < handle

    methods

        % Construction
        function [this] = Piecewise(xmeas, ymeas)
            ... here compute xTrans and coeffMat...
        end

        % Interpolation
        function [yinterp] = Evaluate(xinterp)
            ... Here use previously computed xTrans and coeffMat ...
        end
    end

    properties(SetAccess=Private, GetAccess=Private)
        xTrans;
        coeffMat;
    end

end

In this way you can prcompute xTrans vector and coeffMat matrix during construction and then later reuse these properties when you need to evaluate interpolant at xinterp values in Evaluate method.

% The real measured data
xmeas = ...
ymeas = ...

% Build piecewise interpolation object
piecewise = Piecewise(x,meas, ymeas);

% Rebuild curve at any new positions
xinterp = ...
yinterp = piecewise.Evaluate(xinterp);

Function like syntax

If you truly prefer to have function-handle like syntax, you can still internally use above Piecewise object and add extra static method to return it as a function handle:

classdef Piecewise < handle

    ... see code above...

    methods(Static=true)
        function [f] = MakeHandle(xmeas, ymeas)
        %[
            obj = Piecewise(xmeas, ymeas);
            f = @(x)obj.Evaluate(x);
        %]
    end

end

This can be used like this:

f = Piecewise.MakeHandle(xmeas, ymeas);
yinterp = f(xinterp);

PS1: You can later put Evaluate and Piecewise constructor methods as private if you absolutely wanna to force this syntax.

PS2: To fully hide object oriented design, you can turn MakeHandle into a fully classic routine (will work the same as if static and users won't have to type Piecewise. in front of MakeHandle).

A last solution without oop

Put everything in a single .m file :

function [f] = MakeHandle(xmeas, ymeas)
    ... Here compute xTrans and coeffMat ...
    f = @(x)compute(x, xTrans, coeffMat);% Passing xTrans/coeffMatt as hidden parameters
end
function [yinterp] = compute(xinterp, xTrans, coeffMat)
    ... do interpolation here...
 end
CitizenInsane
  • 4,755
  • 1
  • 25
  • 56
  • Why not? No reason. But to make it work like I want, I would need to extend `subsref` instead of creating the method `Evaluate`. – Dev-iL Mar 02 '15 at 18:19
  • @Dev-iL ... It is possible to redefine `subsref` within the class ... Guess you would like to have a syntax like `piecewise(xinterp)`, right ? – CitizenInsane Mar 02 '15 at 18:28
  • Precisely - see the 2nd consideration I mentioned. I'm aware that it's possible within a class. If you could add that bit I would be happy to accept your answer. – Dev-iL Mar 02 '15 at 18:43
  • @Dev-iL I edited my answer for a 'function-handle' like syntax. – CitizenInsane Mar 02 '15 at 19:03
  • Fair enough. I'll add the subsref version later. – Dev-iL Mar 02 '15 at 19:12
  • @Dev-iL Well I may be not fully inline with what you have in mind but I quite discourage you to use `subsref`, *which normally is a coordinate indexer*, as a method accepting double-vector (everybody that will read-back your code will become crasy trying to understand). Why 'function-like' syntax I added isn't ok for what you want ? – CitizenInsane Mar 02 '15 at 19:18
  • The `subsref` "trick" is a practice employed by MATLAB quite officialy - see http://www.mathworks.com/help/matlab/matlab_oop/a-polynomial-class-1.html#f3-62469 for an example. – Dev-iL Mar 02 '15 at 20:02
0

As an extension of CitizenInsane's answer, the following is an alternative approach that allows a "handle-y" access to the inner Evaluate function.

    function b = subsref(this,s)
       switch s(1).type
          case '()'
             xval = s.subs{:};
             b = this.Evaluate(xval);
          otherwise %// Fallback to the default behavior for '.' and '{}':
             builtin('subsref',this,s);
       end
    end

References: docs1, docs2, docs3, question1

P.S. docs2 is referenced because I initially tried to do subsref@handle (which is calling the superclass method, as one would expect in OOP when overriding methods in a subclass), but this doesn't work in MATLAB, which instead requires builtin() to achieve the same functionality.

Community
  • 1
  • 1
Dev-iL
  • 23,742
  • 7
  • 57
  • 99