#!/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 = 33

# point
class Point
  attr_accessor :x, :y
  def initialize(x = nil, y = nil)
    @x, @y = x, y
  end
end

# model: a*exp(-((x-x0)**2+(y-y0)**2)/2/sigma**2)
gauss_f = Proc.new { |x, t, y, s, f|
  # x: parameters as a Vecor
  # t: observed points as an Array
  # y: observed data as a GSL::Vector
  # s: errorbar
  # f: result
  a = x[0]
  x0 = x[1]
  y0 = x[2]
  sigma2 = x[3]**2
  y.size.times do |i|
    f.set(i, (a*Math::exp(-((t[i].x - x0)**2+(t[i].y - y0)**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]
  y0 = x[2]
  sigma = x[3]
  sigma2 = sigma**2
  y.size.times do |i|
    dx = t[i].x - x0; dx2 = dx**2
    dy = t[i].y - y0; dy2 = dy**2
    f = a*Math::exp(-(dx2+dy2)/2/sigma2)
    df.set(i, 0, f/a/s[i])
    df.set(i, 1, f*dx/sigma2/s[i])
    df.set(i, 2, f*dy/sigma2/s[i])
    df.set(i, 3, f*(dx2+dy2)/sigma2/sigma/s[i])
  end
  GSL::SUCCESS
}

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

# data
t = Array.new
tmin = -10.0
tmax = 10.0
N.times do |j|
  N.times do |i|
    t << Point.new(tmin + (tmax - tmin)*i/(N-1), tmin + (tmax - tmin)*j/(N-1))
  end
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, -0.1, 2.0]) # initial guess
fdf = GSL::MultiFit::Function_fdf.alloc(gauss_f, gauss_df, x.size)
fdf.set_data(t, y, s)

solver = GSL::MultiFit::FdfSolver.alloc(GSL::MultiFit::FdfSolver::LMSDER, t.size, x.size)
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 - xresult.size
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
  # allow resulting parameters to differ two times than standard error
end
puts "a*exp(-((x-x0)**2+(y-y0)**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])
  if (xresult[i] - xgoal[i]).abs > xsigma[i] then
    puts "#{'%-2.2s' % parname[i]} is #{xresult[i]} +- #{xsigma[i]}"
    exit 1
  end
end
