Search code examples
matlabmathmathematical-optimizationcurve-fittingpiecewise

model fitting with MATLAB


I have a set of measures of signal received power (p) at a given distance (d) and a model with unknown coefficients n1 and n2. What I have to do is find the values of n1 and n2 which minimizes the RMS error. I've seen solutions using lsqnonlin but not for piecewise defined functions. I'm a real noob in matlab and I'm having lots of trouble, please help!

The given model is:
model

So far I've defined a model.m script which defines the equation above:

function y = modelo(d,ht,hr,h0,f,data)
y=NaN(size(d));
c=3*10^8;
lambda=c/f;
rbp = 4*(ht-h0)*(hr-h0)/lambda
if d <= rbp
    y = 20*log10(4*pi/lambda)+10*@(x)x(1)*log10(d)-data;
else
    y =20*log10(4*pi/lambda)+10*@(x)x(1)*log10(d) + 10*@(x)x(2)*log10(d)-data;
end

The d parameter is the distances vector and data is the measured power at that distance. All other parameters are fixed numbers.

Then I'm trying to use lsqnonlin but it's not working. Any ideas on how to solve this?


Solution

  • As it appears that I don't have enough reputation to comment on your question and clarify some things I have gone ahead and tried my best to infer what you are trying to do. So let's start.

    Assumptions

    1. lsqnonlinfit minimizes the SSE of the function you pass in. Therefore the function that you'll give to it will have to output difference values (measured - predicted).

    Problems with your code

    1. Your if statements do not check to ensure that the conditions of the piecewise function are met. Your if checks that d <= dbp, but not that dref <= d.
    2. The if statement will not function the way you think it will. when you do something like d <= dbp and d is not a scalar your return will be of the same size as d. The if statement will not work.
    3. This line of code y = 20*log10(4*pi/lambda)+10*@(x)x(1)*log10(d)-data; does not work (neither does the other one). What you are doing here is you are trying to add together a number (20*log10(4*pi/lambda)) and an anonymous function (10*@(x)x(1)*log10(d)-data);
    4. Your modelo function does not have n1 and n2 as parameters that get passed into it.

    My attempt at fixing your modelo function

    Below is the matlab function that should work. There are some important implementation details after the end of the file.

    function diff = modelo(ns, d, p, lambda, dRef, dBp)
    % Extracting n1 and n2 so that code is easier to read.
    n1 = ns(1);
    n2 = ns(2);
    
    % Preallocating the size of PL
    PL = NaN(size(d));
    
    % Indicies for the two sections of the piecewise function 
    i1 = (dRef <= d) & (d <= dBp);
    i2 = (d > dBp);
    
    % Calculating the output value for the d values that fall in the first
    % range
    PL(i1) = ...
        20 .* log10(4 .* pi .* dRef ./ lambda) + ...
        10 .* n1 .* log10(d(i1) ./ dRef);
    
    % Calculating the output values for the d values taht fall in the second
    % range
    PL(i2) = ...
        20 .* log10(4 .* pi .* dRef ./ lambda) + ...
        10 .* n1 .* log10(dBp ./ dRef) + ...
        10 .* n2 + log10(d(i2) ./ dBp);
    
    % Note that by initializing PL to NaN, any values of d outside of the two
    % ranges of this function will result in a output of NaN.
    
    % Calculates the difference between the estimated value (PL) and the
    % measure value (p).
    diff = PL - p;
    end
    

    NOTE: lsqnonlin requires the input function to be of the form func(x) where x is the values that you are fitting for. In this case our modelo function is of the form modelo(ns, d, p, lambda, dRef, dBp). In order to make this work, we'll have to make use of an anonymous function. So the file where you call lsqnonlin from will look something like this:

    % Initial guesses for n1 and n2, n1 will be the 1st entry and n2 the 2nd.
    n0 = ...;
    % Distance data, as a vector
    D = ...;
    % Power data, as a vector
    P = ...;
    % Value of lambda
    LAMBDA = ...;
    % Value of dRef
    DREF = ...;
    % Value of dBp
    DBP = ...;
    
    % Calling lsqnonlin
    ns = lsqnonlin(@(n) modelo(n, D, P, LAMDA, DREF, DBP), n0);
    

    What's going on inside that call to lsqnonlin is that we're converting wrapping the call to modelo inside an anonymous function. The anonymous function takes in a single variable (n) and therefore it fits the requirements of lsqnonlin. Notice that modelo still requires all the other information to be passed to it, and we do this inside the anonymous function. I have capitalized the variable names that get passed into it to emphasize this fact.