/*
  root.c
  Ruby/GSL: Ruby extension library for GSL (GNU Scientific Library)
  (C) Copyright 2004 by Yoshiki Tsunesada
  
  Ruby/GSL is free software: you can redistribute it and/or modify it
  under the terms of the GNU General Public License.
  This library is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY.
*/
#include "rb_gsl_array.h"
#include "rb_gsl_common.h"
#include "rb_gsl_function.h"
#include "rb_gsl_root.h"

#ifndef CHECK_FUNCTION_FDF
#define CHECK_FUNCTION_FDF(x) if(!rb_obj_is_kind_of(x,cgsl_function_fdf))\
    rb_raise(rb_eTypeError, "wrong argument type (Function_fdf expected)");
#endif

static VALUE cgsl_function_fdf;

enum {
  GSL_ROOT_FSOLVER_BISECTION,
  GSL_ROOT_FSOLVER_FALSEPOS,
  GSL_ROOT_FSOLVER_BRENT,
  GSL_ROOT_FDFSOLVER_NEWTON,
  GSL_ROOT_FDFSOLVER_SECANT,
  GSL_ROOT_FDFSOLVER_STEFFENSON,
};

static double rb_gsl_function_fdf_f(double x, void *p);
static void gsl_function_fdf_free(gsl_function_fdf *f);

static double rb_gsl_function_fdf_f(double x, void *p);
static double rb_gsl_function_fdf_df(double x, void *p);
static void rb_gsl_function_fdf_fdf(double x, void *p, double *f, double *df);

static void setfunc(int i, VALUE *argv, gsl_function_fdf *F);
static void setfunc(int i, VALUE *argv, gsl_function_fdf *F)
{
  VALUE ary;
  if (F->params == NULL) {
    ary = rb_ary_new2(4);
    (VALUE) F->params = ary;
  } else {
    ary = (VALUE) F->params;
  }

  if (rb_obj_is_kind_of(argv[i], rb_cProc)) rb_ary_store(ary, i, argv[i]);
  else if (TYPE(argv[i]) == T_ARRAY || rb_obj_is_kind_of(argv[i], cgsl_vector)
	   || TYPE(argv[i]) == T_FIXNUM || TYPE(argv[i]) == T_FLOAT) {
    rb_ary_store(ary, 3, argv[i]);
  } else {
    rb_raise(rb_eArgError, 
	     "wrong type argument (Proc, Array, GSL::Vector or a number)");
  }
}

static void gsl_function_fdf_mark(gsl_function_fdf *f);
static VALUE rb_gsl_function_fdf_new(int argc, VALUE *argv, VALUE klass)
{
  gsl_function_fdf *F = NULL;
  VALUE ary;
  int i;
  F = ALLOC(gsl_function_fdf);
  F->f = &rb_gsl_function_fdf_f;
  F->df = &rb_gsl_function_fdf_df;
  F->fdf = &rb_gsl_function_fdf_fdf;
  ary = rb_ary_new2(4);
  (VALUE) F->params = ary;
  rb_ary_store(ary, 2, Qnil);
  rb_ary_store(ary, 3, Qnil);
  for (i = 0; i < argc; i++) setfunc(i, argv, F);
  return Data_Wrap_Struct(klass, gsl_function_fdf_mark, gsl_function_fdf_free, F);
}

static void gsl_function_fdf_free(gsl_function_fdf *f)
{
  free((gsl_function_fdf *) f);
}

static void gsl_function_fdf_mark(gsl_function_fdf *f)
{
  rb_gc_mark((VALUE) f->params);
}

static VALUE rb_gsl_function_fdf_set(int argc, VALUE *argv, VALUE obj)
{
  gsl_function_fdf *F = NULL;
  VALUE ary;
  int i;
  Data_Get_Struct(obj, gsl_function_fdf, F);
  ary = (VALUE) F->params;
  rb_ary_store(ary, 2, Qnil);
  rb_ary_store(ary, 3, Qnil);
  for (i = 0; i < argc; i++) setfunc(i, argv, F);
  return obj;
}

static VALUE rb_gsl_function_fdf_set_f(VALUE obj, VALUE procf)
{
  gsl_function_fdf *F = NULL;
  VALUE ary;
  CHECK_PROC(procf);
  Data_Get_Struct(obj, gsl_function_fdf, F);
  if (F->params == NULL) {
    ary = rb_ary_new2(4);
    (VALUE) F->params = ary;
  } else {
    ary = (VALUE) F->params;
  }
  rb_ary_store(ary, 0, procf);
  return obj;
}

static VALUE rb_gsl_function_fdf_set_df(VALUE obj, VALUE procdf)
{
  gsl_function_fdf *F = NULL;
  VALUE ary;
  CHECK_PROC(procdf);
  Data_Get_Struct(obj, gsl_function_fdf, F);
  if (F->params == NULL) {
    ary = rb_ary_new2(4);
    (VALUE) F->params = ary;
  } else {
    ary = (VALUE) F->params;
  }
  rb_ary_store(ary, 1, procdf);
  return obj;
}

static VALUE rb_gsl_function_fdf_set_fdf(VALUE obj, VALUE procfdf)
{
  gsl_function_fdf *F = NULL;
  VALUE ary;
  CHECK_PROC(procfdf);
  Data_Get_Struct(obj, gsl_function_fdf, F);
  if (F->params == NULL) {
    ary = rb_ary_new2(4);
    (VALUE) F->params = ary;
  } else {
    ary = (VALUE) F->params;
  }
  rb_ary_store(ary, 2, procfdf);
  return obj;
}

static VALUE rb_gsl_function_fdf_set_params(int argc, VALUE *argv, VALUE obj)
{
  gsl_function_fdf *F = NULL;
  VALUE ary, ary2;
  int i;
  Data_Get_Struct(obj, gsl_function_fdf, F);
  ary = (VALUE) F->params;
  if (argc == 0) return obj;
  if (argc == 1) {
    rb_ary_store(ary, 3, argv[0]);
  } else {
    ary2 = rb_ary_new2(argc);
    for (i = 0; i < argc; i++) rb_ary_store(ary2, i, argv[i]);
    rb_ary_store(ary, 3, ary2);
  }
  return obj;
}

static double rb_gsl_function_fdf_f(double x, void *p)
{
  VALUE result, params, proc, ary;
  ary = (VALUE) p;
  proc = rb_ary_entry(ary, 0);
  params = rb_ary_entry(ary, 3);
  if (NIL_P(params)) result = rb_funcall(proc, ID_call, 1, rb_float_new(x));
  else result = rb_funcall(proc, ID_call, 2, rb_float_new(x), params);
  return NUM2DBL(result);
}

static double rb_gsl_function_fdf_df(double x, void *p)
{
  VALUE result, params, proc, ary;
  ary = (VALUE) p;
  proc = rb_ary_entry(ary, 1);
  params = rb_ary_entry(ary, 3);
  if (NIL_P(params)) result = rb_funcall(proc, ID_call, 1, rb_float_new(x));
  else result = rb_funcall(proc, ID_call, 2, rb_float_new(x), params);
  return NUM2DBL(result);
}

static void rb_gsl_function_fdf_fdf(double x, void *p, double *f, double *df)
{
  VALUE result, params, proc_f, proc_df, proc_fdf, ary;
  ary = (VALUE) p;
  proc_f = rb_ary_entry(ary, 0);
  proc_df = rb_ary_entry(ary, 1);
  proc_fdf = rb_ary_entry(ary, 2);
  params = rb_ary_entry(ary, 3);
  if (NIL_P(proc_fdf)) {
    if (NIL_P(params)) {
      result = rb_funcall(proc_f, ID_call, 1, rb_float_new(x));
      *f = NUM2DBL(result);
      result = rb_funcall(proc_df, ID_call, 1, rb_float_new(x));
      *df = NUM2DBL(result);
    } else {
      result = rb_funcall(proc_f, ID_call, 2, rb_float_new(x), params);
      *f = NUM2DBL(result);
      result = rb_funcall(proc_df, ID_call, 2, rb_float_new(x), params);
      *df = NUM2DBL(result);
    }
  } else {
    if (NIL_P(params)) result = rb_funcall(proc_fdf, ID_call, 1, rb_float_new(x));
    else result = rb_funcall(proc_fdf, ID_call, 2, rb_float_new(x), params);
    *f = NUM2DBL(rb_ary_entry(result, 0));
    *df = NUM2DBL(rb_ary_entry(result, 1));
  }
}

/*****/

static VALUE rb_gsl_fsolver_new(VALUE klass, VALUE t)
{
  gsl_root_fsolver *s = NULL;
  const gsl_root_fsolver_type *T;
  char name[32];
  switch (TYPE(t)) {
  case T_STRING:
    strcpy(name, STR2CSTR(t));
    if (!str_tail_grep(name, "bisection")) {
      T = gsl_root_fsolver_bisection;
    } else if (!str_tail_grep(name, "falsepos")) {
      T = gsl_root_fsolver_falsepos;
    } else if (!str_tail_grep(name, "brent")) {
      T = gsl_root_fsolver_brent;
    } else {
      rb_raise(rb_eTypeError, 
	       "type must be \"bisection\" or \"falsepos\", or \"brent\".");
    }
    break;
  case T_FIXNUM:
    switch (FIX2INT(t)) {
    case GSL_ROOT_FSOLVER_BISECTION:
      T = gsl_root_fsolver_bisection;
      break;
    case GSL_ROOT_FSOLVER_FALSEPOS:
      T = gsl_root_fsolver_falsepos;
      break;
    case GSL_ROOT_FSOLVER_BRENT:
      T = gsl_root_fsolver_brent;
      break;
    default:
      rb_raise(rb_eTypeError, "type must be BISECTION or FALSEPOS, or BRENT.");
      break;
    }
    break;
  default:
    rb_raise(rb_eTypeError, "wrong argument type %s (String or Fixnum expected)",
	     rb_class2name(CLASS_OF(t)));
    break;
  }
  s = gsl_root_fsolver_alloc(T);
  return Data_Wrap_Struct(klass, 0, gsl_root_fsolver_free, s);
}

static VALUE rb_gsl_fsolver_set(VALUE obj, VALUE func, VALUE xl, VALUE xh)
{
  gsl_root_fsolver *s = NULL;
  gsl_function *fff = NULL;
  double xlow, xup;
  Need_Float(xl); Need_Float(xh);
  CHECK_FUNCTION(func);
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  Data_Get_Struct(func, gsl_function, fff);
  xlow = NUM2DBL(xl);
  xup = NUM2DBL(xh);
  gsl_root_fsolver_set(s, fff, xlow, xup);
  return obj;
}

static VALUE rb_gsl_fsolver_iterate(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return INT2FIX(gsl_root_fsolver_iterate(s));
}

static VALUE rb_gsl_fsolver_root(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_float_new(gsl_root_fsolver_root(s));
}

static VALUE rb_gsl_fsolver_x_lower(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_float_new(gsl_root_fsolver_x_lower(s));
}

static VALUE rb_gsl_fsolver_x_upper(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_float_new(gsl_root_fsolver_x_upper(s));
}

static VALUE rb_gsl_fsolver_name(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_str_new2(gsl_root_fsolver_name(s));
}

static VALUE rb_gsl_fsolver_test_interval(VALUE obj, VALUE eabs, VALUE erel)
{
  gsl_root_fsolver *s = NULL;
  Need_Float(eabs); Need_Float(erel); 
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return INT2FIX(gsl_root_test_interval(s->x_lower, s->x_upper,
					NUM2DBL(eabs), NUM2DBL(erel)));
}

static VALUE rb_gsl_root_test_interval(VALUE obj, VALUE xl, VALUE xu, VALUE eabs,
				       VALUE erel)
{
  Need_Float(xl); Need_Float(xu);
  Need_Float(eabs); Need_Float(erel); 
  return INT2FIX(gsl_root_test_interval(NUM2DBL(xl), NUM2DBL(xu),
					NUM2DBL(eabs), NUM2DBL(erel)));
}

static VALUE rb_gsl_root_test_delta(VALUE obj, VALUE xl, VALUE xu, VALUE eabs,
				       VALUE erel)
{
  Need_Float(xl); Need_Float(xu);
  Need_Float(eabs); Need_Float(erel); 
  return INT2FIX(gsl_root_test_delta(NUM2DBL(xl), NUM2DBL(xu),
				     NUM2DBL(eabs), NUM2DBL(erel)));
}

static VALUE rb_gsl_root_test_residual(VALUE obj, VALUE xl,VALUE eabs)
{
  Need_Float(xl); Need_Float(eabs);
  return INT2FIX(gsl_root_test_residual(NUM2DBL(xl),  NUM2DBL(eabs)));
}

static VALUE rb_gsl_fsolver_solve(int argc, VALUE *argv, VALUE *obj)
{
  gsl_root_fsolver *s = NULL;
  gsl_function *F = NULL;
  double x, xl, xh, epsabs = 0.0, epsrel = 1e-6;
  int status, iter = 0, max_iter = 100;
  switch (argc) {
  case 3:
    Check_Type(argv[2], T_ARRAY);
    epsabs = NUM2DBL(rb_ary_entry(argv[2], 0));
    epsrel = NUM2DBL(rb_ary_entry(argv[2], 1));
    /* no break */
  case 2:
    Check_Type(argv[1], T_ARRAY);
    xl = NUM2DBL(rb_ary_entry(argv[1], 0));
    xh = NUM2DBL(rb_ary_entry(argv[1], 1));
    break;
  default:
    rb_raise(rb_eArgError, 
	     "Usage: solve(f = Function, range = Array, eps = Array)");
    break;
  }
  CHECK_FUNCTION(argv[0]);
  Data_Get_Struct(argv[0], gsl_function, F);
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  gsl_root_fsolver_set(s, F, xl, xh);
  do {
    iter++;
    status = gsl_root_fsolver_iterate (s);
    x = gsl_root_fsolver_root (s);
    xl = gsl_root_fsolver_x_lower (s);
    xh = gsl_root_fsolver_x_upper (s);
    status = gsl_root_test_interval (xl, xh, epsabs, epsrel);
    if (status == GSL_SUCCESS) break;
  } while (status == GSL_CONTINUE && iter < max_iter);
  return rb_float_new(x);
}

static VALUE rb_gsl_fdfsolver_new(VALUE klass, VALUE t)
{
  gsl_root_fdfsolver *s = NULL;
  const gsl_root_fdfsolver_type *T;
  char name[32];
  switch (TYPE(t)) {
  case T_STRING:
    strcpy(name, STR2CSTR(t));
    if (!str_tail_grep(name, "newton")) {
      T = gsl_root_fdfsolver_newton;
    } else if (!str_tail_grep(name, "secant")) {
      T = gsl_root_fdfsolver_secant;
    } else if (!str_tail_grep(name, "steffenson")) {
      T = gsl_root_fdfsolver_steffenson;
    } else {
      rb_raise(rb_eTypeError, "type must be NEWTON or SECANT, or STEFFENSON.");
    }
    break;
  case T_FIXNUM:
    switch (FIX2INT(t)) {
    case GSL_ROOT_FDFSOLVER_NEWTON:
      T = gsl_root_fdfsolver_newton;
      break;
    case GSL_ROOT_FDFSOLVER_SECANT:
      T = gsl_root_fdfsolver_secant;
      break;
    case GSL_ROOT_FDFSOLVER_STEFFENSON:
      T = gsl_root_fdfsolver_steffenson;
      break;
    default:
      rb_raise(rb_eTypeError, "type must be NEWTON or SECANT, or STEFFENSON.");
      break;
    }
    break;
  default:
    rb_raise(rb_eTypeError, "wrong argument type %s (String or Fixnum expected)",
	     rb_class2name(CLASS_OF(t)));
    break;
  }
  s = gsl_root_fdfsolver_alloc(T);
  return Data_Wrap_Struct(klass, 0, gsl_root_fdfsolver_free, s);
}

static VALUE rb_gsl_fdfsolver_set(VALUE obj, VALUE func, VALUE r)
{
  gsl_root_fdfsolver *s = NULL;
  gsl_function_fdf *fff = NULL;
  double root;
  CHECK_FUNCTION_FDF(func);
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  Data_Get_Struct(func, gsl_function_fdf, fff);
  root = NUM2DBL(r);
  gsl_root_fdfsolver_set(s, fff, root);
  return obj;
}

static VALUE rb_gsl_fdfsolver_iterate(VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  return INT2FIX(gsl_root_fdfsolver_iterate(s));
}

static VALUE rb_gsl_fdfsolver_root(VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  return rb_float_new(gsl_root_fdfsolver_root(s));
}

static VALUE rb_gsl_fdfsolver_name(VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  return rb_str_new2(gsl_root_fdfsolver_name(s));
}

static VALUE rb_gsl_fdfsolver_solve(int argc, VALUE *argv, VALUE *obj)
{
  gsl_root_fdfsolver *s = NULL;
  double x = 0.0, x0, epsabs = 0.0, epsrel = 1e-6;
  gsl_function_fdf *F = NULL;
  int status, iter = 0, max_iter = 100;
  switch (argc) {
  case 3:
    Check_Type(argv[2], T_ARRAY);
    epsabs = NUM2DBL(rb_ary_entry(argv[2], 0));
    epsrel = NUM2DBL(rb_ary_entry(argv[2], 1));
    /* no break */
  case 2:
    Need_Float(argv[1]);
    x0 = NUM2DBL(argv[1]);
    break;
  default:
    rb_raise(rb_eArgError, "Usage: solve(f = Function, range = Array, eps = Array)");
    break;
  }
  CHECK_FUNCTION_FDF(argv[0]);
  Data_Get_Struct(argv[0], gsl_function_fdf, F);
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  gsl_root_fdfsolver_set(s, F, x0);  
  do {
    iter++;
    status = gsl_root_fdfsolver_iterate (s);
    x0 = x;
    x = gsl_root_fdfsolver_root (s);
    status = gsl_root_test_delta(x, x0, epsabs, epsrel);
    if (status == GSL_SUCCESS) break;
  } while (status == GSL_CONTINUE && iter < max_iter);
  return rb_float_new(x);
}

static VALUE rb_gsl_function_rootfinder(int argc, VALUE *argv, VALUE obj)
{
  int status, iter = 0, max_iter = 100;
  const gsl_root_fsolver_type *T;
  gsl_root_fsolver *s = NULL;
  gsl_function *F = NULL;
  double r, a, b, epsabs = 0.0, epsrel = 1e-6;
  Data_Get_Struct(obj, gsl_function, F);
  switch (argc) {
  case 3:
    CHECK_FIXNUM(argv[2]);
    max_iter = FIX2INT(argv[2]);
    /* no break */
  case 2:
    switch (TYPE(argv[1])) {
    case T_ARRAY:
      epsabs = NUM2DBL(rb_ary_entry(argv[1], 0));
      epsrel = NUM2DBL(rb_ary_entry(argv[1], 1));
      break;
    default:
      rb_raise(rb_eTypeError, "errors must be given by an array [epsabs, epsrel]");
    }
      /* no break */
  case 1:
    switch (TYPE(argv[0])) {
    case T_ARRAY:
      a = NUM2DBL(rb_ary_entry(argv[0], 0));
      b = NUM2DBL(rb_ary_entry(argv[0], 1));
      break;
    default:
      rb_raise(rb_eTypeError, "interval must be given by an array [a, b]");
    }
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 2)", argc);
    break;
  }
  T = gsl_root_fsolver_brent;
  s = gsl_root_fsolver_alloc (T);
  gsl_root_fsolver_set (s, F, a, b);
  do {
    iter++;
    status = gsl_root_fsolver_iterate (s);
    r = gsl_root_fsolver_root (s);
    a = gsl_root_fsolver_x_lower (s);
    b = gsl_root_fsolver_x_upper (s);
    status = gsl_root_test_interval (a, b, epsabs, epsrel);
    if (status == GSL_SUCCESS) break;
  } while (status == GSL_CONTINUE && iter < max_iter);
  gsl_root_fsolver_free(s);
  if (status == GSL_SUCCESS) {
    return rb_float_new(r);
  } else {
    printf("not converged\n");
    return Qfalse;
  }
}

void Init_gsl_root()
{
  VALUE mgsl_root;
  VALUE cgsl_fsolver;
  VALUE cgsl_fdfsolver;

  mgsl_root = rb_define_module_under(mgsl, "Root");

  cgsl_function_fdf = rb_define_class_under(mgsl, "Function_fdf", rb_cObject);
  rb_define_singleton_method(cgsl_function_fdf, "new", rb_gsl_function_fdf_new, -1);
  rb_define_singleton_method(cgsl_function_fdf, "alloc", rb_gsl_function_fdf_new, -1);
  rb_define_method(cgsl_function_fdf, "set", rb_gsl_function_fdf_set, -1);
  rb_define_method(cgsl_function_fdf, "set_f", rb_gsl_function_fdf_set_f, 1);
  rb_define_method(cgsl_function_fdf, "set_df", rb_gsl_function_fdf_set_df, 1);
  rb_define_method(cgsl_function_fdf, "set_fdf", rb_gsl_function_fdf_set_fdf, 1);
  rb_define_method(cgsl_function_fdf, "set_params", rb_gsl_function_fdf_set_params, -1);

  cgsl_fsolver = rb_define_class_under(mgsl_root, "FSolver", rb_cObject);
  rb_define_singleton_method(cgsl_fsolver, "new", rb_gsl_fsolver_new, 1);  

  rb_define_method(cgsl_fsolver, "set", rb_gsl_fsolver_set, 3);  
  rb_define_method(cgsl_fsolver, "iterate", rb_gsl_fsolver_iterate, 0);  
  rb_define_method(cgsl_fsolver, "root", rb_gsl_fsolver_root, 0);  
  rb_define_method(cgsl_fsolver, "name", rb_gsl_fsolver_name, 0);  
  rb_define_method(cgsl_fsolver, "x_lower", rb_gsl_fsolver_x_lower, 0);  
  rb_define_method(cgsl_fsolver, "x_upper", rb_gsl_fsolver_x_upper, 0);  
  rb_define_method(cgsl_fsolver, "test_interval", rb_gsl_fsolver_test_interval, 2);  
  rb_define_method(cgsl_fsolver, "solve", rb_gsl_fsolver_solve, -1);  

  rb_define_singleton_method(mgsl_root, "test_interval", 
			    rb_gsl_root_test_interval, 4);
  rb_define_singleton_method(mgsl_root, "test_delta", 
			    rb_gsl_root_test_delta, 4);
  rb_define_singleton_method(mgsl_root, "test_residual", 
			    rb_gsl_root_test_residual, 2);

  cgsl_fdfsolver = rb_define_class_under(mgsl_root, "FdfSolver", rb_cObject);
  rb_define_singleton_method(cgsl_fdfsolver, "new", rb_gsl_fdfsolver_new, 1);  

  rb_define_method(cgsl_fdfsolver, "set", rb_gsl_fdfsolver_set, 2);  
  rb_define_method(cgsl_fdfsolver, "iterate", rb_gsl_fdfsolver_iterate, 0);  
  rb_define_method(cgsl_fdfsolver, "root", rb_gsl_fdfsolver_root, 0);  
  rb_define_method(cgsl_fdfsolver, "name", rb_gsl_fdfsolver_name, 0);  
  rb_define_method(cgsl_fdfsolver, "solve", rb_gsl_fdfsolver_solve, -1);  

  rb_define_method(cgsl_function, "fsolve", rb_gsl_function_rootfinder, -1);
  rb_define_alias(cgsl_function, "solve", "fsolve");

  rb_define_const(cgsl_fsolver, "BISECTION", INT2FIX(GSL_ROOT_FSOLVER_BISECTION));
  rb_define_const(cgsl_fsolver, "FALSEPOS", INT2FIX(GSL_ROOT_FSOLVER_FALSEPOS));
  rb_define_const(cgsl_fsolver, "BRENT", INT2FIX(GSL_ROOT_FSOLVER_BRENT));
  rb_define_const(cgsl_fsolver, "Bisection", INT2FIX(GSL_ROOT_FSOLVER_BISECTION));
  rb_define_const(cgsl_fsolver, "Falsepos", INT2FIX(GSL_ROOT_FSOLVER_FALSEPOS));
  rb_define_const(cgsl_fsolver, "Brent", INT2FIX(GSL_ROOT_FSOLVER_BRENT));

  rb_define_const(cgsl_fdfsolver, "NEWTON", INT2FIX(GSL_ROOT_FDFSOLVER_NEWTON));
  rb_define_const(cgsl_fdfsolver, "SECANT", INT2FIX(GSL_ROOT_FDFSOLVER_SECANT));
  rb_define_const(cgsl_fdfsolver, "STEFFENSON", INT2FIX(GSL_ROOT_FDFSOLVER_STEFFENSON));
  rb_define_const(cgsl_fdfsolver, "Newton", INT2FIX(GSL_ROOT_FDFSOLVER_NEWTON));
  rb_define_const(cgsl_fdfsolver, "Secant", INT2FIX(GSL_ROOT_FDFSOLVER_SECANT));
  rb_define_const(cgsl_fdfsolver, "Steffenson", INT2FIX(GSL_ROOT_FDFSOLVER_STEFFENSON));
}

#undef CHECK_FUNCTION_FDF
