Fitting an exponential curve

Many sites have the solution to the case where one wants to fit an exponential of the form A e ^ {B x}. In general one can just take the log of the function and fit the straight line that results. What about data where you don’t know the y-intersect, i.e. of the form: A + B e ^ {C x}. We are trying to guess A, so we can’t just subtract it.

Skipping the detail, wrote a matlab script to solve this, some people might find it useful.

function [numresult,CIinfos] = fit(xdata,ydata,stdvar)
%result = getfit(xdata,ydata,variance)
% xdata = xcoordinates
% ydata = ycoordinates
% stdvar = variance of data points
% ALL COLUMN VECTORS PLEASE!
%
% numresult gives in the first column: estimates of the parameters (e.g. intercept and gradient)
%                        second and third column: estimates of the parameters' standard error confidence intervals
% CIinfos gives the parameter values at the limits of the confidence intervals, for each parameter.
%
% There's plenty of things that need tweaking in the code, it's not plug-and-play.
%
% e.g. mess with rangesize and rangeres to decide on what values to look at when deciding on the CIs.
% 
% three functions are available, comment out the one you want:
% MEAN - one parameter (the mean)
% LINEAR - two parameters (intersept & gradient)
% EXPONENT - three parameters, A, B and C in A+Be^{Cx}

options = optimset('TolX',0.05);

if (size(xdata,1)<size(xdata,2))
 error('Please use column vectors, not row vectors');
end

%MEAN
%Nparams = 1;
%func = @(a)(-sum( log( normpdf((ydata-a(1))./sqrt(stdvar),0,1)./sqrt(stdvar) ) ));
%[numresult,res]=fminsearch(func,[1]);

%LINEAR
%Nparams = 2;
%func = @(a)(-sum( log( normpdf((ydata-(a(1)+a(2)*xdata))./sqrt(stdvar),0,1)./sqrt(stdvar) ) ));
%[numresult,res]=fminsearch(func,[1,1]);

%EXPONENT
Nparams = 3;
%more likely to give Inf (as log(norm(X)) depends on norm giving non-zero results).
%func = @(a)(-sum( log( normpdf((ydata-( a(1)+a(2)*exp(a(3)*xdata)) )./sqrt(stdvar),0,1)./sqrt(stdvar) ) ));
%this will be more robust...
temp = @(a)(   ydata-( a(1)+a(2)*exp(a(3)*xdata))   );
func = @(a)(sum(   temp(a).^2 ./ (2*stdvar)    )    );
[result,res]=fminsearch(func,[max(ydata),-max(ydata),-.5]);
result = result';
numresult = result;
numresult

for param=1:Nparams
 fprintf('Parameter: %i\n',param);
 testa = result; %set testa to the numerical best-fit result
 ps = [];

 rangesize = 8+2*abs(result(param)); %assume that the range we should look over is roughly of the order of the parameter...
 rangeres = 3000;
 paramvalrange = result(param)-rangesize:rangesize/rangeres:result(param)+rangesize; %range of values we'll try

 counter = 0;
 for paramval = paramvalrange
  counter=counter+1; if (mod(counter,10)==0); fprintf('%i%% ',round(100*counter/rangeres)); end;
  testa(param,1) = paramval;
%find the best result while fixing this parameter...
  [temp,x] = fminsearch(@(a) func([a(1:param-1);paramval;a(param+1:end)]),testa,options);
%not necessarily the best result: the sample distribution may not be symmetric
%ie, this assumes the two parameters are independent!
%  x = func(testa);
  ps(end+1) = -x;
 end
 ps = exp(ps);
 if (sum(ps)==0)
  subplot(2,1,1);
  plot(ps);
  subplot(2,1,2);
  plot(xdata,ydata,'x');
  fprintf('Best guess: ');
  error('Probabilities all zero');
 end
 ps = ps./sum(ps);
 cumps = cumsum(ps);
 calcCI = [paramvalrange(find(diff(cumps>0.1587))),paramvalrange(find(diff(cumps>0.8413)))];

%find the parameters that fit this CI limit the best (just run the minsearch again)
 for ci_i=1:2
  [CIinfo] = fminsearch(@(a) func([a(1:param-1);calcCI(ci_i);a(param+1:end)]),testa,options);
  CIinfos{param,ci_i} = CIinfo;
 end

 numresult(param,2) = calcCI(1);
 numresult(param,3) = calcCI(2);

 if (1)
  subplot(Nparams,1,param);
  plot(paramvalrange,ps);
  grid;
  title(sprintf('Parameter %i',param));
  hold on;
  plot([calcCI(1),calcCI(1)],[0,max(ps)],'k-');
  plot([calcCI(2),calcCI(2)],[0,max(ps)],'k-');
 end
end

figure;
errorbar(xdata,ydata,sqrt(stdvar),'k+-');
hold on;
xs=[min(xdata):0.1:max(xdata)];
ys=result(1)+result(2)*exp(result(3)*xs);
plot(xs,ys,'b-');

for param=1:3
 for ci_i=1:2
  CIinfos{param,ci_i}(param,1) = numresult(param,ci_i+1);
  nr = CIinfos{param,ci_i};
  ys=nr(1,1)+nr(2,1)*exp(nr(3,1)*xs);	
  plot(xs,ys,'b:');
 end
end

There is also a test function, which tests that 69% of the test data is within the CIs the fitting function returns:

%make some test data parameters
xdata=[1,2,3,4,5,6,7,8,9,10]';
a=[10,-10,-.4]; %parameters for curve
stdvar = abs(rand(size(xdata)))+1; %noisiness of data points
stdvar = stdvar./10;

guesses = [];
rs = [];
for it=1:1000
%Linear:
% ydata = a(1)+a(2)*xdata + randn(size(xdata)).*sqrt(stdvar);

%Exponential:
 ydata = a(1)+a(2)*exp(a(3)*xdata) + randn(size(xdata)).*sqrt(stdvar);

 r = getfit_numerical(xdata,ydata,stdvar);
 rs(it,:) = r(:,1)';
 for param = 1:size(r,1)
  guesses(it,param) = (r(param,2)<a(param)) & (r(param,3)>a(param));
 end
end

mean(guesses)

For the mean and straight-line solutions the probability distributions of the parameters are

Parameter confidence intervals

Parameter confidence intervals: parameter 1 is the asymptote (if it is an exponential decay), parameter 2 is the height of the curve (from x axis to asymptote), parameter 3 is the exponential curve’s parameter.

(as we already know) normal. For the exponential fit however, they become very non-normal with very long tails. This seemed like a bug at first. But if one considers the extreme of the first parameter: Very large values can be ‘cancelled out’ by very negative values of the 2nd parameter (and a smaller value of the 3rd parameter). This would show up as something closer to a straight line through the points. Plotting the 6 limits to the 3 parameters (and selecting the maximum-likelihood for each other pair of parameters) reveals this. In the figure below the dotted lines illustrate these extremes: Note that a reasonable fit is achieved by lines extending from an almost straight line to highly curved (almost-vertical-then-horizontal) solutions.

Hope this comes in useful!

Fitting exponential curve to a set of points.

Fitting exponential curve to a set of points (each with an associated error value). The CIs indicate 1 standard error, the 6 dotted lines are the 3 pairs of CIs for the 3 parameters.

Borders Bike Ride

Route from Edinburgh, along route 196, over Fala Moor, to Heriot and the Border's Loop, right at Stow, Stantling Craig Reservoir,

Route from Edinburgh, along route 196, over Fala Moor, to Heriot and the Border’s Loop, right at Stow, past Stantling Craig then right along NSN1 to Innerleithen, up Glentress then over the top and down back to Edinburgh.

For Lyndsey’s birthday we hired a tandem from Laid Back Bikes for a cycle to the Borders. LaidBack lent us the Circe Helios. We’re not bike-experts at all, so can’t give any details! But in general: 1. It was very comfortable, with lots of adjustable components so it could fit both of us happily. The wheels are quite small, but, after a few seconds of practice, steering became easy. The saddles were both comfy, and the ‘thud-buster’ did a good job at keeping the bumps away from the person over the back wheel. We were planning on camping – normally we have enough space in our two panniers and with a couple of bungies could carry everything on our bikes. We realised however we didn’t have enough space on the tandem! Luckily LaidBack had the solution, a bike trailer made by carry freedom. I think the wheels are the same size as the Tandems! We went over some quite rugged terrain on our ride, but we didn’t have any trouble with the bike or trailer. I was particularly impressed with how quiet the trailer was – the only hint it was there was the extra 2 wheels humming on the tarmac. It will probably be my next addition to my bike-stuff.

IMG_1636

Lyndsey with the tandem on Fala Moor.

Our ride started in Marchmont at Laid Back’s shop, then headed along Cycle Route 1, through the innocence tunnel, eventually reaching route 196. This follows a disused rail line (the Pentcaitland Railway Walk) which used to collect the coal from the mines in East Lothian. At the end of this turn right (leaving route 196) and make your way across the quiet country lanes to Fala. From there take the narrow road up, past the cemetery and on to Fala Moor (google street view’s been up there!).

To be continued…

A Cycle Ride

Rode this route last year, and put together the details for a friend. Figured I should share it with the internet too.

Cycle route around the countryside near Edinburgh (38 miles)

The cycle route, total distance 38 miles. Approximately 365m ascent. Took us 4.5 hours of cycling over a 9 hour day.

The ride goes as far out as Musselburgh in one direction, then south, through Dalkeith, Roslin, to the Pentlands, through the Pentlands down the water of Leith and back.

I’ve tried to list all the reasonable cafes on the route.

Please download the pdf (11Mb! 5 pages).

Plotter

Has your ink-jet printer stopped working? Does the ink cost too much? If the answer to either of these questions is Yes, then you need to build yourself a plotter!

Standard ink-jet printers have a couple of stepper motors inside. These are probably the most expensive bit of the printer (besides the cartridges) and are worth rescuing!

A few years ago I built a stepper motor driver from normal transistors (i.e. not field effect), to drive an inverted pendulum. this was a stressful experience as the transistors got pretty hot and the circuit had to handle the problems of back EMF and it needed a logic chip anyway to turn the signals from the PC into useful base inputs for the transistors. Inverted Pendulum: http://www.youtube.com/watch?v=wyk9RVbJakw

MTS62C19A

The MTS62C19A is a stepper motor driver chip. Easy to use! But only comes in surface mount packages.

Anyway, there’s a cheap, simple chip to do all this for you, the MTS62C19A. In its most simple mode you’ll just need half-a-dozen capacitors and resistors and the example circuit in the datasheet (watch out that you get datasheet Revision B or later as they mislabelled the two Rs resistors on the circuit diagram).

Using the basic configuration ‘full step’ mode, is really simple, as only two inputs are needed Phase 1 and Phase 2. This is what’s running at the moment.

However, there are drawbacks:

  • large step size
  • noisy
  • massive/inefficient power usage

To avoid this it’s worth switching to a microstepping configuration. In this, you’ll need 4 digital inputs (Phase 1 & 2 and I0x  & I1x), and two analogue.

I’ve not done this step yet, but I figure the analogue outputs can be fairly crude. In the datasheet the two inputs only have 7 input levels each, so only need 3 bits into a resistor ladder. I wonder if there’s a set of ladder values specifically designed for making sine/cosine steps?

Plotter (sorry about poor/wobbly video!) https://vimeo.com/61815544

Fuel Duty

Frustrated by the lack of actual info on fuel duty (is it on average going up or down when corrected for inflation?)

Found inflation info and the duty changes (see attached document).

Results: (pence per litre)

Fuel duty changes 2001-2012

Fuel duty changes 2001-2012

In summary the last 2 years have seen a reduction in fuel duty. In general it’s been fairly constant over the last 10 years – although without an increase in the next 6 months we’ll be reaching the lowest duty for decades (in real terms).

xls spreadsheet: fuelduty

The Alternative Olympics

My local Amnesty International group recently needed a new website. This meant digging through old photos and looking back at what we’d done.

I started campaigning with the Edinburgh group five or six years ago, before the 2008 Olympic games. One of the most fun, but least successful events was our “alternative Olympics”, part of our China campaign – looking at and educating people about the human rights situation in China.

22242_471535825611_4945518_n

The alternative Olympics, 2008.

Later that year, Rebiya Kadeer, a prominent campaigner for the rights of the Uyghur people, visited Edinburgh on a speaking tour of the UK. I’d visited Xinjiang before, but my memories of it were restricted to Urumqi and were of a gloomy wintery city. The lines of people made to stand in the city’s central square for hours on the night we left still frightens me. A country like China usually prefers to hide its worst abuses, but here they had lined up several hundred people in temperatures as low as -10C for hours – patrolled by armed soldiers the people stood, unmoving. I didn’t have the nerve to approach. The photos were taken from the train station, hidden from view.

hr1

Taken 5th December, 2005

hr2

Taken 5th December, 2005

The region was anexed in 1949, and so it seems similar to Tibet in its recent history. While Tibet is a well know region with a famous campaigner in the Dalai Lama, Xinjiang was never found by the Western media. Maybe this was due to the stories and films made of Tibet, compared to the unknown region hemmed in by the USSR and China for most of the 20th Century?

Kadeer had been through the Chinese prison system and, when I spoke to her, much of her family were still being held. Non-violent campaigners for justice, like Kadeer are often described as being unassuming and modest. It was how I found Kadeer – I unexpectedly had a half-hour to chat with her, in a surreal journey on the top of a tour-bus around Edinburgh. She asked me what I did, and about my visit to Urumqi. I was embarrassed to say so little, my time there had been focused on finding a way across the border, and the cold, bleak, concrete, militarised city had seemed unimaginably different from the town we were touring. We spoke through a translator, who I also asked questions of – he also would struggle to return due to his activism, but was willing to make such sacrifices. After meeting her, as many people say, I felt humbled. I still regularly think about her when campaigning for human rights. Hoping our small actions can make a difference.