#!/usr/bin/env ruby
# vim: set ts=8 sw=2 sts=2:
#require("gsl")
#require("../gsl_test2.rb")
require("./test_multifit.rb")
include GSL::Test
include Math

Maxiter = 10
N = 1000

# model: a*exp(-(x-x0)**2/2/sigma**2)
gauss_p = 3
gauss_f = Proc.new { |x, t, y, s, f|
  # x: parameters as a Vecor
  # t: observed points as a GSL::Vector
  # y: observed data as a GSL::Vector
  # s: errorbar
  # f: result
  a = x[0]
  x0 = x[1]
  sigma2 = x[2]**2
  y.size.times do |i|
    f.set(i, (a*Math::exp(-(t[i] - x0)**2/2/sigma2) - y[i])/s[i])
  end
  GSL::SUCCESS
}

gauss_df = Proc.new { |x, t, y, s, df|
  a = x[0]
  x0 = x[1]
  sigma = x[2]
  sigma2 = sigma**2
  y.size.times do |i|
    dx = t[i] - x0
    dx2 = dx**2
    f = a*Math::exp(-dx2/2/sigma2)
    df.set(i, 0, f/a/s[i])
    df.set(i, 1, f*dx/sigma2/s[i])
    df.set(i, 2, f*dx2/sigma2/sigma/s[i])
  end
  GSL::SUCCESS
}

# goal
xgoal = GSL::Vector.alloc([1, 0, 1])
parname = %w( a x0 si )

# data
t = GSL::Vector.alloc(N) # positions of data
tmin = -10.0
tmax = 10.0
t.size.times do |i|
  t[i] = tmin + (tmax - tmin)*i/(N-1)
end
stdev = xgoal[0]*0.1 
s = GSL::Vector.alloc(Array.new(t.size, stdev))  # error bar of each datum
r = Rng.alloc
e = GSL::Vector.alloc(t.size)
t.size.times do |i|
  e[i] = -r.gaussian(stdev)	# perturbation to data
end
y = GSL::Vector.alloc(t.size)
n = GSL::Vector.alloc(Array.new(t.size, 1.0))
gauss_f.call(xgoal, t, e, n, y)  # data: y = model - e

# fitting
x = GSL::Vector.alloc([0.5, 0.1, 2]) # initial guess
fdf = GSL::MultiFit::Function_fdf.alloc(gauss_f, gauss_df, gauss_p)
fdf.set_data(t, y, s)

solver = GSL::MultiFit::FdfSolver.alloc(GSL::MultiFit::FdfSolver::LMSDER, t.size, gauss_p)
solver.set(fdf, x)

solver.print_state(0)
Maxiter.times do |i|
  solver.iterate
  status = solver.test_delta(1e-6, 1e-6)
  solver.print_state(i+1)
  break unless GSL::CONTINUE == status
end

# results
covar = solver.covar(0.0)
xresult = solver.position
dof = t.size - gauss_p
chi2 = pow_2(solver.f.dnrm2)
xsigma = GSL::Vector.alloc(xresult.size)
xresult.size.times do |i|
  xsigma[i] = Math::sqrt(chi2/dof*covar[i,i]) * 2.0 
  # resulting parameters to differ two times than standard error
end
puts("a*exp(-(x-x0)**2/2/si**2), chi2/N:%.3g" % (chi2/t.size))
xresult.size.times do |i|
  test_rel(xresult[i], xgoal[i], xsigma[i], "%-2.2s" % parname[i])
  exit 1 if (xresult[i] - xgoal[i]).abs > xsigma[i]
end
