/* This is -*- C -*- */
/* vim: set sw=2: */
/* $Id: guppi-polynomial.c,v 1.7 2002/01/14 05:01:23 trow Exp $ */

/*
 * guppi-polynomial.c
 *
 * Copyright (C) 2001 The Free Software Foundation

 * Contains code
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Brian Gough
 *
 * Otherwise developed by Jon Trowbridge <trow@gnu.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <config.h>
#include "guppi-polynomial.h"

#include <gtk/gtk.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <guppi-memory.h>

static GtkObjectClass *parent_class = NULL;

enum {
  CHANGED,
  LAST_SIGNAL
};

static guint guppi_polynomial_signals[LAST_SIGNAL] = { 0 };

typedef struct _GuppiPolynomialPrivate GuppiPolynomialPrivate;
struct _GuppiPolynomialPrivate {
  gint d;     /* degree */
  gint N;     /* allocated degree */
  double *c;  /* coefficients */

  gint num_roots;
  double *roots;
  
  gint num_minmax;
  double *minmax;

  gboolean process_lock;
  gint freeze_count;
  gboolean touched;
};

#define priv(x) ((GuppiPolynomialPrivate*)(GUPPI_POLYNOMIAL((x))->opaque_internals))

#define POLYEPSILON 1e-12

static void
guppi_polynomial_finalize (GtkObject *obj)
{
  GuppiPolynomial *x = GUPPI_POLYNOMIAL(obj);
  GuppiPolynomialPrivate *p = priv (x);

  guppi_free0 (p->c);
  guppi_free0 (p->roots);
  guppi_free0 (p->minmax);

  g_free (x->opaque_internals);
  x->opaque_internals = NULL;

  guppi_finalized (obj);

  if (parent_class->finalize)
    parent_class->finalize (obj);
}

static void
guppi_polynomial_class_init (GuppiPolynomialClass *klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *)klass;

  parent_class = gtk_type_class (GTK_TYPE_OBJECT);

  object_class->finalize = guppi_polynomial_finalize;

  /* Signal definition template */
  guppi_polynomial_signals[CHANGED] =
    gtk_signal_new ("changed",
                    GTK_RUN_FIRST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GuppiPolynomialClass, changed),
                    gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);

  gtk_object_class_add_signals (object_class, guppi_polynomial_signals,
                                LAST_SIGNAL);
}

static void
guppi_polynomial_init (GuppiPolynomial *obj)
{
  GuppiPolynomialPrivate *p = g_new0 (GuppiPolynomialPrivate, 1);
  obj->opaque_internals = p;

  p->num_roots = -1;
  p->num_minmax = -1;

  p->d = 0;
  p->N = 2;
  p->c = guppi_new0 (double, p->N+1);
  p->c[0] = 1.0;
}

GtkType
guppi_polynomial_get_type (void)
{
  static GtkType guppi_polynomial_type = 0;
  if (!guppi_polynomial_type) {
    static const GtkTypeInfo guppi_polynomial_info = {
      "GuppiPolynomial",
      sizeof (GuppiPolynomial),
      sizeof (GuppiPolynomialClass),
      (GtkClassInitFunc)guppi_polynomial_class_init,
      (GtkObjectInitFunc)guppi_polynomial_init,
      NULL, NULL, (GtkClassInitFunc)NULL
    };
    guppi_polynomial_type = gtk_type_unique (GTK_TYPE_OBJECT, &guppi_polynomial_info);
  }
  return guppi_polynomial_type;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static inline void
guppi_polynomial_reset_cache (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p = priv (poly);

  p->num_roots = -1;
  guppi_free0 (p->roots);

  p->num_minmax = -1;
  guppi_free0 (p->minmax);
}

static inline void
guppi_polynomial_changed (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p = priv (poly);

  if (p->freeze_count > 0) {
    p->touched = TRUE;
  } else {
    gtk_signal_emit (GTK_OBJECT (poly), guppi_polynomial_signals[CHANGED]);
    p->touched = FALSE;
  }
}

static void
guppi_polynomial_sanitize (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p = priv (poly);

  while (p->d > 0 && fabs (p->c[p->d]) < POLYEPSILON) {
    --p->d;
    p->touched = TRUE;
  }
    
}

static void
guppi_polynomial_grow (GuppiPolynomial *poly, gint N)
{
  GuppiPolynomialPrivate *p = priv (poly);

  if (N <= p->N)
    return;

  p->c = guppi_realloc (p->c, sizeof (double) * (N+1));
  p->N = N;
}

static void
guppi_polynomial_polish_cached_roots (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p = priv (poly);
  gint i;

  if (p->num_roots <= 0 || p->roots == NULL)
    return;

  for (i=0; i < p->num_roots; ++i) {
    p->roots[i] = guppi_polynomial_newton_polish (poly, p->roots[i], 5, POLYEPSILON);
  }
}

static gint
double_cmp (const void *a, const void *b)
{
  return (*(double *)a < *(double *)b) - (*(double *)a > *(double *)b);
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

GuppiPolynomial *
guppi_polynomial_new (gint degree, ...)
{
  GuppiPolynomial *poly;
  GuppiPolynomialPrivate *p;
  va_list args;
  gint i;

  g_return_val_if_fail (degree >= 0, NULL);

  poly = gtk_type_new (GUPPI_TYPE_POLYNOMIAL);
  p = priv (poly);

  guppi_polynomial_grow (poly, degree);
  p->d = degree;

  va_start (args, degree);
  for (i=0; i<degree+1; ++i) {
    p->c[i] = va_arg (args, double);
  }
  va_end (args);
  
  guppi_polynomial_sanitize (poly);

  return poly;
}

GuppiPolynomial *
guppi_polynomial_newv (gint degree, const double *vec)
{
  GuppiPolynomial *poly;
  GuppiPolynomialPrivate *p;
  gint i;

  g_return_val_if_fail (degree >= 0, NULL);
  g_return_val_if_fail (vec, NULL);

  poly = gtk_type_new (GUPPI_TYPE_POLYNOMIAL);
  p = priv (poly);

  guppi_polynomial_grow (poly, degree);
  p->d = degree;

  for (i=0; i<degree+1; ++i) {
    p->c[i] = vec[i];
  }

  guppi_polynomial_sanitize (poly);

  return poly;
}

GuppiPolynomial *
guppi_polynomial_new_by_roots (gint degree, ...)
{
  GuppiPolynomial *poly;
  va_list args;
  gint i;

  g_return_val_if_fail (degree >= 0, NULL);

  poly = guppi_polynomial_new_constant (1);
  guppi_polynomial_freeze (poly);
  guppi_polynomial_grow (poly, degree);

  va_start (args, degree);
  for (i=0; i<degree; ++i) {
    guppi_polynomial_inflate (poly, va_arg (args, double));
  }
  va_end (args);

  guppi_polynomial_thaw (poly);

  return poly;
}

GuppiPolynomial *
guppi_polynomial_new_by_rootsv (gint degree, const double *vec) 
{
  GuppiPolynomial *poly;
  gint i;

  g_return_val_if_fail (degree >= 0, NULL);

  poly = guppi_polynomial_new_constant (1);
  guppi_polynomial_freeze (poly);
  guppi_polynomial_grow (poly, degree);

  for (i=0; i<degree; ++i) {
    guppi_polynomial_inflate (poly, vec[i]);
  }

  guppi_polynomial_thaw (poly);

  return poly;
}


GuppiPolynomial *
guppi_polynomial_new_constant (double c0)
{
  return guppi_polynomial_new (0, c0);
}

GuppiPolynomial *
guppi_polynomial_new_linear (double c0, double c1)
{
  return guppi_polynomial_new (1, c0, c1);
}

GuppiPolynomial *
guppi_polynomial_new_quadratic (double c0, double c1, double c2)
{
  return guppi_polynomial_new (2, c0, c1, c2);
}

GuppiPolynomial *
guppi_polynomial_copy (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  GuppiPolynomial *cpy;
  GuppiPolynomialPrivate *cpy_p;

  if (poly == NULL)
    return NULL;

  p = priv (poly);

  cpy = guppi_polynomial_newv (p->d, p->c);
  cpy_p = priv (cpy);

  cpy_p->num_roots = p->num_roots;
  if (p->num_roots > 0) {
    cpy_p->roots = guppi_new0 (double, p->num_roots);
    memcpy (cpy_p->roots, p->roots, sizeof (double) * p->num_roots);
  }

  cpy_p->num_minmax = p->num_minmax;
  if (p->num_minmax > 0) {
    cpy_p->minmax = guppi_new0 (double, p->num_minmax);
    memcpy (cpy_p->minmax, p->minmax, sizeof (double) * p->num_minmax);
  }

  return cpy;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

void
guppi_polynomial_freeze (GuppiPolynomial *poly)
{
  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  ++ priv (poly)->freeze_count;
}

void
guppi_polynomial_thaw (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);
  g_return_if_fail (p->freeze_count > 0);
  --p->freeze_count;
  if (p->freeze_count == 0 && p->touched)
    guppi_polynomial_changed (poly);
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

gint
guppi_polynomial_degree (GuppiPolynomial *poly)
{
  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), -1);

  return priv (poly)->d;
}

double
guppi_polynomial_coefficient (GuppiPolynomial *poly, gint i)
{
  GuppiPolynomialPrivate *p;
  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), 0);
  p = priv (poly);

  return (0 <= i && i <= p->d) ? p->c[i] : 0;
}

void
guppi_polynomial_set_coefficient (GuppiPolynomial *poly, gint i, double c)
{
  GuppiPolynomialPrivate *p;
  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  g_return_if_fail (i >= 0);
  p = priv (poly);

  if (fabs (c - (i <= p->d ? p->c[i] : 0)) < POLYEPSILON)
    return;

  if (i > p->d && fabs (c) > POLYEPSILON) {
    guppi_polynomial_grow (poly, i);
    p->d = i;
  }

  if (i <= p->d)
    p->c[i] = c;

  guppi_polynomial_reset_cache (poly);
  if (i == p->d)
    guppi_polynomial_sanitize (poly);
  guppi_polynomial_changed (poly);
}

double
guppi_polynomial_eval (GuppiPolynomial *poly, double x)
{
  GuppiPolynomialPrivate *p;
  double run;
  gint i;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), 0);
  p = priv (poly);

  i = p->d;
  run = p->c[i];
  --i;
  while (i >= 0) {
    run = run * x + p->c[i];
    --i;
  }
  return run;
}

double
guppi_polynomial_eval_D (GuppiPolynomial *poly, double x)
{
  GuppiPolynomialPrivate *p;
  double run;
  gint i;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), 0);
  p = priv (poly);

  if (p->d == 0)
    return 0;
  
  i = p->d;
  run = i * p->c[i];
  --i;
  while (i >= 1) {
    run = run * x + i * p->c[i];
    --i;
  }
  return run;
}

double
guppi_polynomial_eval_DD (GuppiPolynomial *poly, double x)
{
  GuppiPolynomialPrivate *p;
  double run;
  gint i;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), 0);
  p = priv (poly);

  if (p->d < 2)
    return 0;
  
  i = p->d;
  run = i * (i-1) * p->c[i];
  --i;
  while (i >= 2) {
    run = run * x + i * (i-1) * p->c[i];
    --i;
  }
  return run;
}

void
guppi_polynomial_sample (GuppiPolynomial *poly, gint N,
			 const double *x, gint x_stride,
			 double *y, gint y_stride)
{
  GuppiPolynomialPrivate *p;
  gint i, j;
  double run, x0;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  if (N == 0)
    return;
  g_return_if_fail (N > 0);
  g_return_if_fail (x != NULL);
  g_return_if_fail (y != NULL);

  p = priv (poly);

  for (j=0; j < N; ++j) {
    x0 = *x;

    i = p->d;
    run = p->c[i];
    --i;

    while (i >= 0) {
      run = run * x0 + p->c[i];
      --i;
    }

    *y = run;

    x  = (const double *)( ((gchar *)x) + x_stride );
    y  =       (double *)( ((gchar *)y) + y_stride );
  }
}

void
guppi_polynomial_sample_uniformly (GuppiPolynomial *poly,
				   double a, double b, gsize N,
				   double *x, gint x_stride,
				   double *y, gint y_stride)
{
  GuppiPolynomialPrivate *p;
  gint i, j;
  double run, x0;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  if (N == 0)
    return;
  g_return_if_fail (N > 0);

  if (x == NULL && y == NULL)
    return;

  p = priv (poly);
  
  for (j=0; j < N; ++j) {
    x0 = a + j*(b-a)/(N-1);

    i = p->d;
    run = p->c[i];
    --i;

    while (i >= 0) {
      run = run * x0 + p->c[i];
      --i;
    }

    if (x) {
      *x = x0;
      x  = (double *)( ((gchar *)x) + x_stride );
    }
    if (y) {
      *y = run;
      y  = (double *)( ((gchar *)y) + y_stride );
    }

  }
}

#define APPROX_PATH_MAX 1000
#define ROOT_BUCKET_MAX 200
ArtVpath *
guppi_polynomial_approximate_path (GuppiPolynomial *poly,
				   double a, double b,
				   double min_y, double max_y,
				   double x_error, double y_error,
				   double min_angle,
				   double scale_x, double scale_y)
{
  GuppiPolynomial *Dpoly;
  ArtVpath path_seg[APPROX_PATH_MAX], *path;
  double root_bucket[ROOT_BUCKET_MAX];
  gint j, p=0, pos, N, DN;
  GList *subdiv = NULL;
  gboolean done = FALSE;
  GList *i, *next_i;
  double midx, midy, x0, y0, x1, y1, dx, dy, tx, ty, normt;
  double rxa, rya, rxb, ryb, s, s0, s1, s2, s3, ms, Ms, m, M;
  double trx0, try0, trx1, try1; /* transformed coordinates */

  Dpoly = guppi_polynomial_copy (poly);
  guppi_polynomial_D (Dpoly);
  
  path_seg[p].x = a;
  path_seg[p].y = guppi_polynomial_eval (poly, a);
  subdiv = g_list_append (subdiv, &path_seg[p]);
  ++p;

  /*
    We always want to sample at the maxima and minima.  This keeps the plotted curve
    from "wobbling" when we translate it.
  */
  if (guppi_polynomial_degree (Dpoly) < ROOT_BUCKET_MAX) {
    DN = guppi_polynomial_find_real_roots (Dpoly, root_bucket);
    for (j=0; j<DN; ++j) {
      if (j > 0 && fabs (root_bucket[j] - root_bucket[j-1]) < POLYEPSILON)
	continue;

      if (a <= root_bucket[j] && root_bucket[j] <= b) {
	path_seg[p].x = root_bucket[j];
	path_seg[p].y = guppi_polynomial_eval (poly, root_bucket[j]);
	if (min_y <= path_seg[p].y && path_seg[p].y <= max_y) {
	  subdiv = g_list_append (subdiv, &path_seg[p]);
	  ++p;
	}
      }
    }
  }

  path_seg[p].x = b;
  path_seg[p].y = guppi_polynomial_eval (poly, b);
  subdiv = g_list_append (subdiv, &path_seg[p]);
  ++p;

  while (!done) {
    done = TRUE;

    pos = 0;
    i = subdiv;
    while ((next_i = g_list_next (i)) && pos < APPROX_PATH_MAX) {

      x0 = ((ArtVpath *) i->data)->x;
      y0 = ((ArtVpath *) i->data)->y;

      x1 = ((ArtVpath *) next_i->data)->x;
      y1 = ((ArtVpath *) next_i->data)->y;

      trx0 = scale_x * x0;
      try0 = scale_y * y0;

      trx1 = scale_x * x1;
      try1 = scale_y * y1;

      if (fabs (trx1 - trx0) > x_error) {
	
	guppi_polynomial_minmax_on_range (poly, x0, x1, &m, &M);

	if (min_y <= M && m <= max_y) {
	
	  midx = (trx0 + trx1)/2;
	  midy = (try0 + try1)/2;

	  dx = trx1 - trx0;
	  dy = try1 - try0;

	  tx = -dy;
	  ty =  dx;
	  normt = sqrt(tx*tx + ty*ty);
	  tx *= x_error / normt;
	  ty *= y_error / normt;

	  rxa = midx + tx;
	  rya = midy + ty;

	  rxb = midx - tx;
	  ryb = midy - ty;
      
	  s  = (try1 - try0) / (trx1 - trx0);
	  s0 = (rya - try0)  / (rxa - trx0);
	  s1 = (rya - try1)  / (rxa - trx1);
	  s2 = (ryb - try0)  / (rxb - trx0);
	  s3 = (ryb - try1)  / (rxb - trx1);
	  
	  ms = Ms = s;
	  if (s0 < ms) ms = s0;
	  if (Ms < s0) Ms = s0;
	  if (s1 < ms) ms = s1;
	  if (Ms < s1) Ms = s1;
	  if (s2 < ms) ms = s2;
	  if (Ms < s2) Ms = s2;
	  if (s3 < ms) ms = s3;
	  if (Ms < s3) Ms = s3;
	  
	  guppi_polynomial_minmax_on_range (Dpoly, x0, x1, &m, &M);

	  m *= scale_y / scale_x;
	  M *= scale_y / scale_x;
	  
	  if (m < ms || Ms < M) {
	    path_seg[p].x = (x0+x1)/2;
	    path_seg[p].y = guppi_polynomial_eval (poly, (x0+x1)/2);
	    ++pos;
	    subdiv = g_list_insert (subdiv, &path_seg[p], pos);
	    ++p;
	    done = FALSE;
	  }
	}
      }      
      i = next_i;
      ++pos;
    }
  }

  N = g_list_length (subdiv);
  path = g_new0 (ArtVpath, N+1);
  i = subdiv;
  j = 0;
  while (i != NULL && j < N) {
    memcpy (&path[j], i->data, sizeof (ArtVpath));
    path[j].code = j ? ART_LINETO : ART_MOVETO;
    i = g_list_next (i);
    ++j;
  }
  path[j].code = ART_END;
  g_print ("samples: %d\n", N);

  g_list_free (subdiv);
  guppi_unref0 (Dpoly);
  
  return path;
}

void
guppi_polynomial_scale (GuppiPolynomial *poly, double c)
{
  GuppiPolynomialPrivate *p;
  gint i;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  if (fabs (c-1) < POLYEPSILON)
    return;

  for (i = 0; i <= p->d; ++i)
    p->c[i] *= c;

  /* roots and minima/maxima are not affected by scaling. */
  guppi_polynomial_sanitize (poly);
  guppi_polynomial_changed (poly);
}

void
guppi_polynomial_D (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  gint i;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  if (p->d == 0) {
    gboolean changed = fabs (p->c[0]) > POLYEPSILON;
    p->c[0] = 0;
    if (changed)
      goto done;
    return;
  }

  for (i = 1; i <= p->d; ++i) {
    p->c[i-1] = i * p->c[i];
  }
   
  --p->d;

 done:
  guppi_polynomial_reset_cache (poly);
  guppi_polynomial_changed (poly);
}

void
guppi_polynomial_normalize (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  guppi_polynomial_scale (poly, 1 / p->c[p->d]);
}

void
guppi_polynomial_modulo (GuppiPolynomial *poly, GuppiPolynomial *mod)
{
  GuppiPolynomialPrivate *p;
  GuppiPolynomialPrivate *mod_p;
  double factor;
  gint i;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  g_return_if_fail (mod && GUPPI_IS_POLYNOMIAL (mod));

  p = priv (poly);
  mod_p = priv (mod);

  if (mod_p->d == 0) {
    p->c[0] = 0;
    p->d = 0;
  }

  if (p->d < mod_p->d)
    return;
  
  while (p->d >= mod_p->d) {
    factor = p->c[p->d] / mod_p->c[mod_p->d];

    if (fabs (factor) > POLYEPSILON) {
      for (i=0; i<=mod_p->d; ++i)
	p->c[p->d - mod_p->d + i] -= factor * mod_p->c[i];
    }

    --p->d;
    guppi_polynomial_sanitize (poly);
  }

  guppi_polynomial_reset_cache (poly);
  guppi_polynomial_changed (poly);
}

void
guppi_polynomial_deflate (GuppiPolynomial *poly, double x0)
{
  GuppiPolynomialPrivate *p;
  gint i;
  double old, t;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  if (p->d == 0)
    return;

  old = p->c[p->d-1];
  p->c[p->d-1] = p->c[p->d];
  for (i = p->d-2; i >= 0; --i) {
    t = p->c[i];
    p->c[i] = p->c[i+1] * x0 + old;
    old = t;
  }
  --p->d;

  guppi_polynomial_sanitize (poly);
  guppi_polynomial_reset_cache (poly);
  guppi_polynomial_changed (poly);
}

void
guppi_polynomial_deflate_complex (GuppiPolynomial *poly, double re, double im)
{
  GuppiPolynomialPrivate *p;
  gint i;
  double a, b, older, old, t;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);
  if (p->d < 2)
    return;

  /* We don't handle this case yet. */
  g_assert (p->d != 2);

  a = 2*re;
  b = -(re*re + im*im);

  older = p->c[p->d-2];
  old   = p->c[p->d-3];

  p->c[p->d-2] = p->c[p->d];
  p->c[p->d-3] = p->c[p->d-1] + a * p->c[p->d-2];

  for (i = p->d-4; i >= 0; --i) {
    t = p->c[i];

    p->c[i] = older + a * p->c[i+1] + b * p->c[i+2];
    
    older = old;
    old = t;
  }

  p->d -= 2;

  guppi_polynomial_sanitize (poly);
  guppi_polynomial_reset_cache (poly);
  guppi_polynomial_changed (poly);
}

void
guppi_polynomial_inflate (GuppiPolynomial *poly, double x0)
{
  GuppiPolynomialPrivate *p;
  gint i;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  guppi_polynomial_grow (poly, p->d+1);

  p->c[p->d+1] = p->c[p->d];
  for (i = p->d; i > 0; --i) {
    p->c[i] = p->c[i-1] - x0 * p->c[i];
  }
  p->c[0] *= -x0;

  ++p->d;

  guppi_polynomial_reset_cache (poly);
  guppi_polynomial_changed (poly);
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

/* There two functions are taken from GSL.  Thanks to Brian Gough. */

/* Solve a x^2 + b x + c == 0 */

static gint
solve_quadratic (double a, double b, double c,
		 double *x)
{
  double disc = b * b - 4 * a * c;

  if (disc > 0) {
      if (b == 0) {
          double r = fabs (0.5 * sqrt (disc) / a);
          x[0] = -r;
          x[1] =  r;
      } else {
          double sgnb = (b > 0 ? 1 : -1);
          double temp = -0.5 * (b + sgnb * sqrt (disc));
          double r1 = temp / a;
          double r2 = c / temp;

          if (r1 < r2) {
	    x[0] = r1;
	    x[1] = r2;
	  } else {
	    x[0] = r2;
	    x[1] = r1;
	  }
      }
      return 2;
  } else if (disc == 0) {
    x[0] = -0.5 * b / a;
    return 1;
  } else {
    return 0;
  }
}

#define GSL_SWAP(a,b) do { double tmp = b ; b = a ; a = tmp ; } while(0)

/* Solve x^3 + a x^2 + b x + c == 0. */

static gint
solve_cubic (double a, double b, double c,double *x)
{
  double q = (a * a - 3 * b);
  double r = (2 * a * a * a - 9 * a * b + 27 * c);

  double Q = q / 9;
  double R = r / 54;

  double Q3 = Q * Q * Q;
  double R2 = R * R;

  double CR2 = 729 * r * r;
  double CQ3 = 2916 * q * q * q;

  if (R == 0 && Q == 0) {

    x[0] = - a / 3;
    return 1;

  } else if (CR2 == CQ3) {
    /* this test is actually R2 == Q3, written in a form suitable
       for exact computation with integers */

    /* Due to finite precision some double roots may be missed, and
       considered to be a pair of complex roots z = x +/- epsilon i
       close to the real axis. */

    double sqrtQ = sqrt (Q);

    if (R > 0) {
      x[0] = -2 * sqrtQ  - a / 3;
      x[1] = sqrtQ - a / 3;
    } else {
      x[0] = - sqrtQ  - a / 3;
      x[1] = 2 * sqrtQ - a / 3;
    }
    return 2;
  
  } else if (CR2 < CQ3) { /* equivalent to R2 < Q3 */

    double sqrtQ = sqrt (Q);
    double sqrtQ3 = sqrtQ * sqrtQ * sqrtQ;
    double theta = acos (R / sqrtQ3);
    double norm = -2 * sqrtQ;
    x[0] = norm * cos (theta / 3) - a / 3;
    x[1] = norm * cos ((theta + 2.0 * M_PI) / 3) - a / 3;
    x[2] = norm * cos ((theta - 2.0 * M_PI) / 3) - a / 3;
    
    /* Sort *x0, *x1, *x2 into increasing order */

    if (x[0] > x[1])
      GSL_SWAP(x[0], x[1]);
    
    if (x[1] > x[2]) {
      GSL_SWAP(x[1], x[2]);
	
      if (x[0] > x[1])
	GSL_SWAP(x[0], x[1]);
    }
    
    return 3;

  } else {

    double sgnR = (R >= 0 ? 1 : -1);
    double A = -sgnR * pow (fabs (R) + sqrt (R2 - Q3), 1.0/3.0);
    double B = Q / A;
    x[0] = A + B - a / 3;
    return 1;

  }

}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

/*** Special handling for low-degree polynomials ***/

static void
process_deg0 (GuppiPolynomialPrivate *p)
{
  p->num_roots = 0;
  p->num_minmax = 0;
}

static void
process_deg1 (GuppiPolynomialPrivate *p)
{
  p->num_roots = 1;
  p->roots = guppi_new0(double, 1);
  p->roots[0] = -p->c[0] / p->c[1];

  p->num_minmax = 0;
}

static void
process_deg2 (GuppiPolynomialPrivate *p)
{
  p->roots = guppi_new0 (double, 2);
  p->num_roots = solve_quadratic (p->c[2], p->c[1], p->c[0], 
				       p->roots);

  p->num_minmax = 1;
  p->minmax = guppi_new0 (double, 1);
  p->minmax[0] = -p->c[0] / (2*p->c[1]);
}

static void
process_deg3 (GuppiPolynomialPrivate *p)
{
  double C = p->c[3];
  p->roots = guppi_new0 (double, 3);

  p->num_roots = solve_cubic (p->c[2] / C,
				   p->c[1] / C,
				   p->c[0] / C,
				   p->roots);

  p->minmax = guppi_new0 (double, 2);
  p->num_minmax = solve_quadratic (3 * p->c[3],
				   2 * p->c[1],
				   p->c[0],
				   p->minmax);
}

static void
process_deg4 (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  GuppiPolynomial *q;
  GuppiPolynomialPrivate *q_p;
  gboolean have_root;
  double x0, C;

  p = priv (poly);

  /* Find the first root numerically, then deflate and solve the cubic. */
  
  have_root = guppi_polynomial_find_one_real_root (poly, &x0);
  if (have_root) {

    p->roots = guppi_new0 (double, 4);
    p->roots[0] = x0;
    p->num_roots = 1;

    q = guppi_polynomial_copy (poly);
    q_p = priv (q);
    guppi_polynomial_deflate (q, x0);

    C = priv (q)->c[3];
    p->num_roots += solve_cubic (q_p->c[2] / C,
				 q_p->c[1] / C,
				 q_p->c[0] / C,
				 p->roots+1);

    guppi_unref (q);

    guppi_polynomial_polish_cached_roots (poly);
    
  } else {

    p->num_roots = 0;

  }

  p->minmax = guppi_new0 (double, 3);
  C = 4 * p->c[4];
  p->num_minmax = solve_cubic (3 * p->c[3] /  C,
			       2 * p->c[2] /  C,
			       p->c[1] / C,
			       p->minmax);
}

static gboolean
process_switch (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  gboolean rv = FALSE;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), FALSE);
  p = priv (poly);

  if (p->process_lock)
    return FALSE;

  p->process_lock = TRUE;

  switch (p->d) {

  case 0:
    process_deg0 (p);
    rv = TRUE;
    break;

  case 1:
    process_deg1 (p);
    rv = TRUE;
    break;

  case 2:
    process_deg2 (p);
    rv = TRUE;
    break;

  case 3:
    process_deg3 (p);
    rv = TRUE;
    break;

  case 4:
    process_deg4 (poly);
    rv = TRUE;
    break;
  }

  p->process_lock = FALSE;

  return rv;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

double
guppi_polynomial_newton_polish (GuppiPolynomial *poly, double x, gint iter, double epsilon)
{
  GuppiPolynomialPrivate *p;
  double best_x, best_abs_y;
  double fx, Dfx, abs_fx;
  gint i;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), x);
  p = priv (poly);

  if (epsilon <= 0)
    epsilon = POLYEPSILON;

  best_x = x;
  best_abs_y = fabs (guppi_polynomial_eval (poly, best_x));

  while (iter > 0 && best_abs_y > epsilon) {
    
    /* We simultaneously evaluate the polynomial
       and its first derivative. */
    i = p->d;
    fx = p->c[i];
    Dfx = i * p->c[i];
    --i;
    while (i >= 1) {
      fx = fx * x + p->c[i];
      Dfx = Dfx * x + i * p->c[i];
      --i;
    }
    fx = fx * x + p->c[0];

    abs_fx = fabs (fx);

    if (abs_fx < best_abs_y) {
      best_abs_y = abs_fx;
      best_x = x;
    }

    if (fabs (Dfx) < epsilon)
      break;

    x -= fx / Dfx;

    --iter;
  }

  return best_x;
}

double
guppi_polynomial_gershgorin_radius (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  double max_c, top_c, q;
  gint i;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), 0);
  p = priv (poly);
  
  if (p->d == 0)
    return 0;

  max_c = 0;
  top_c = p->c[p->d];
  for (i = 0; i < p->d; ++i) {
    q = fabs (p->c[i] / top_c);
    if (q > max_c)
      max_c = q;
  }
  
  return max_c + 1;
}

gint
guppi_polynomial_real_roots (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), -1);
  p = priv (poly);
  
  if (p->num_roots < 0) {

    if (! process_switch (poly)) {
      double R = guppi_polynomial_gershgorin_radius (poly);
      p->num_roots = guppi_polynomial_real_roots_in_interval (poly, -R, R);
    }

  }

  return p->num_roots;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gint
sign_changes (GuppiPolynomial **sturm_seq, double a)
{
  gint i, count=0;
  double x, prev_x=0;
  gboolean started = FALSE;

  for (i = 0; sturm_seq[i]; ++i) {
    x = guppi_polynomial_eval (sturm_seq[i], a);
    if (fabs (x) > POLYEPSILON) {
      if (started && x * prev_x < 0)
	++count;
      prev_x = x;
      started = TRUE;
    }
  }

  return count;
}

static GuppiPolynomial **
build_sturm_sequence (GuppiPolynomial *poly)
{
  GuppiPolynomial **sturm_seq;
  gint i;

  sturm_seq = guppi_new0 (GuppiPolynomial *, guppi_polynomial_degree (poly)+2);

  sturm_seq[0] = guppi_polynomial_copy (poly);

  if (guppi_polynomial_degree (poly) <= 1)
    return sturm_seq;

  sturm_seq[1] = guppi_polynomial_copy (poly);
  guppi_polynomial_D (sturm_seq[1]);

  for (i=2; guppi_polynomial_degree (sturm_seq[i-1]) > 0; ++i) {
    sturm_seq[i] = guppi_polynomial_copy (sturm_seq[i-2]);
    guppi_polynomial_modulo (sturm_seq[i], sturm_seq[i-1]);
    guppi_polynomial_scale (sturm_seq[i], -1);
  }

  return sturm_seq;
}

static void
free_sturm_sequence (GuppiPolynomial **seq)
{
  gint i;
  
  if (seq) {
    for (i = 0; seq[i]; ++i) 
      guppi_unref (seq[i]);
  }

  guppi_free (seq);
}

gint
guppi_polynomial_real_roots_in_interval (GuppiPolynomial *poly, double a, double b)
{
  GuppiPolynomialPrivate *p;
  gint Fa, Fb;
  GuppiPolynomial **sturm_seq;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), -1);
  p = priv (poly);

  if (p->roots == NULL)
    process_switch (poly);

  if (p->roots != NULL) {
    gint i, count=0;
    for (i = 0; i < p->num_roots; ++i) {
      if (a <= p->roots[i] && p->roots[i] <= b)
	++count;
    }
    return count;
  }

  sturm_seq = build_sturm_sequence (poly);

  Fa = sign_changes (sturm_seq, a);
  Fb = sign_changes (sturm_seq, b);

  free_sturm_sequence (sturm_seq);

  return abs (Fa - Fb);
}

gboolean
guppi_polynomial_find_one_real_root (GuppiPolynomial *poly, double *root)
{
  GuppiPolynomialPrivate *p;
  GuppiPolynomial **sturm_seq = NULL;
  gboolean found = FALSE, bracketed = FALSE;
  double epsilon_width = 1e-3;
  double epsilon_error;
  double a, b, x=0, y, ya=0, yb=0;
  gint Fa, Fb, Fx;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), FALSE);
  p = priv (poly);

  /* Handle special cases. */

  if (p->num_roots < 0)
    process_switch (poly);

  if (p->num_roots == 0) {
    return FALSE;
  }

  if (p->roots) {
    if (root)
      *root = p->roots[0];
    return TRUE;
  }

  /* Do things the hard way. */

  sturm_seq = build_sturm_sequence (poly);
  b = guppi_polynomial_gershgorin_radius (poly);
  a = -b;

  epsilon_error = MIN (b/100, POLYEPSILON);

  Fa = sign_changes (sturm_seq, a);
  Fb = sign_changes (sturm_seq, b);

  if (Fa == Fb)
    goto done;

  /* Yes, there is a root to be found... */  
  found = TRUE;

  if (root == NULL)
    return TRUE;

  while (b-a > epsilon_width) {

    x = (a+b)/2;
    y = guppi_polynomial_eval (poly, x);
    if (fabs (y) < epsilon_error) {
      *root = x;
      goto done;
    }

    Fx = sign_changes (sturm_seq, x);

    if (Fa != Fx) {
      b = x;
      Fb = Fx;
    } else if (Fb != Fx) {
      a = x;
      Fa = Fx;
    } else {
      g_assert_not_reached ();
    }

    /* Check to see if we've managed to bracket a root with different
       signs on either side. */
    if (abs (Fa - Fb) == 1) {
      ya = guppi_polynomial_eval (poly, a);
      yb = guppi_polynomial_eval (poly, b);
      if (ya * yb < 0) {
	bracketed = TRUE;
	break;
      }
    }

  }

  /* Finish up by subdividing to find our bracketed root. */
  if (bracketed) {
    while (b - a > epsilon_width) {

      x = (a+b)/2;
      y = guppi_polynomial_eval (poly, x);
      if (fabs (y) < epsilon_error) {
	*root = y;
	goto done;
      }
      
      if (ya * y > 0) {
	a = x;
	ya = y;
      } else {
	b = x;
	yb = y;
      }
    }
  }

  /* Polish up our root */
  *root = guppi_polynomial_newton_polish (poly, x, 10, epsilon_error);

 done:
  free_sturm_sequence (sturm_seq);
  return found;
}

gint
guppi_polynomial_find_real_roots (GuppiPolynomial *poly, double *roots)
{
  GuppiPolynomialPrivate *p;
  GuppiPolynomial *q;
  GuppiPolynomialPrivate *q_p;
  double x0;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), -1);
  p = priv (poly);

  if (roots == NULL)
    return guppi_polynomial_real_roots (poly);

  if (p->num_roots < 0 || (p->num_roots > 0 && p->roots == NULL))
    process_switch (poly);

  if (p->num_roots == 0) {
    return 0;
  } else if (p->num_roots > 0 && p->roots) {
    memcpy (roots, p->roots, sizeof (double) * p->num_roots);
    return p->num_roots;
  }

  q = guppi_polynomial_copy (poly);
  q_p = priv (q);

  p->num_roots = 0;
  while (guppi_polynomial_find_one_real_root (q, &x0)) {

    /* If we've got all of the roots, grab them all rather than limiting ourselves to
       just one. */
    if (q_p->num_roots == 0 || (q_p->num_roots > 0 && q_p->roots)) {
      memcpy (roots+p->num_roots, q_p->roots, sizeof (double) * q_p->num_roots);
      p->num_roots += q_p->num_roots;
      break;
    }

    roots[p->num_roots] = x0;
    ++p->num_roots;
    guppi_polynomial_deflate (q, x0);
  }
  guppi_unref (q);

  /* Cache root information. */
  if (p->num_roots > 0 && p->roots == NULL) {
    p->roots = guppi_new (double, p->num_roots);
    memcpy (p->roots, roots, sizeof (double) * p->num_roots);
  }

  /* Make sure our roots are all nice and shiny, then copy the polished roots back out
     to the user's array. */
  if (p->num_roots > 0) {
    guppi_polynomial_polish_cached_roots (poly);
    memcpy (roots, p->roots, sizeof (double) * p->num_roots);
  }

  /* sort our roots */
  if (p->roots)
    qsort (p->roots, p->num_roots, sizeof (double), double_cmp);

  return p->num_roots;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static void
guppi_polynomial_cache_minmax (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p = priv (poly);
  GuppiPolynomial *Dp;
  
  if (p->num_minmax == 0 || (p->num_minmax > 0 && p->minmax != NULL))
    return;
  
  Dp = guppi_polynomial_copy (poly);
  guppi_polynomial_D (Dp);

  p->minmax = guppi_new (double, p->d - 1);
  p->num_minmax = guppi_polynomial_find_real_roots (Dp, p->minmax);

  guppi_unref (Dp);
}

void
guppi_polynomial_minmax_on_range (GuppiPolynomial *poly, double a, double b,
				  double *min, double *max)
{
  GuppiPolynomialPrivate *p;
  double va, vb, m, M;
  gint i;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  /* Optimizations for low degees. */

  if (p->d == 0) {
    if (min) *min = p->c[0];
    if (max) *max = p->c[0];
    return;
  }

  if (p->d == 1) {
    double v1 = p->c[0] + a * p->c[1];
    double v2 = p->c[0] + b * p->c[1];
    if (min) *min = MIN (v1, v2);
    if (max) *max = MAX (v1, v2);
    return;
  }

  /* The general case. */

  guppi_polynomial_cache_minmax (poly);

  va = guppi_polynomial_eval (poly, a);
  vb = guppi_polynomial_eval (poly, b);

  m = MIN (va, vb);
  M = MAX (va, vb);

  for (i=0; i<p->num_minmax; ++i) {
    double x = p->minmax[i], y;
    if (a < x && x < b) {
      y = guppi_polynomial_eval (poly, x);
      if (y < m)
	m = y;
      if (M < y)
	M = y;
    }
  }

  if (min)
    *min = m;
  if (max)
    *max = M;
}

gboolean
guppi_polynomial_find_bounded_range (GuppiPolynomial *poly, double x, double min, double max,
				     double *a, double *b)
{
  GuppiPolynomialPrivate *p;
  double y;
  gint i, mm_i;
  const gint max_iter = 10;

  double a0, a1, b0, b1;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), FALSE);
  p = priv (poly);

  if (guppi_polynomial_degree (poly) == 0)
    return FALSE;

  y = guppi_polynomial_eval (poly, x);
  if (y < min || y > max)
    return FALSE;

  guppi_polynomial_cache_minmax (poly);

  mm_i = 0;
  while (mm_i < p->num_minmax && x <= p->minmax[mm_i]) {
      ++mm_i;
  }

  a0 = a1 = b0 = b1 = x;

  if (a) {
    for (i = mm_i; i > 0; --i) {
      a1 = a0;
      a0 = p->minmax[i];
      y = guppi_polynomial_eval (poly, a0);
      if (y < min || max < y)
	break;
    }

    if (i <= 0) {
      double q = 1;
      while (1) {
	a1 = a0;
	a0 = a0 - q;
	q *= 2;
	y = guppi_polynomial_eval (poly, a0);
	if (y < min || max < y)
	  break;
      }
    }

    for (i=0; i<max_iter && a1 - a0 > POLYEPSILON; ++i) {
      double mid_x = (a0+a1)/2;
      y = guppi_polynomial_eval (poly, mid_x);

      if (min <= y && y <= max) {
	a1 = mid_x;
      } else {
	a0 = mid_x;
      }
    }

    *a = a1;
  }

  if (b) {

    for (i = mm_i+1; i < p->num_minmax; ++i) {
      b0 = b1;
      b1 = p->minmax[i];
      y = guppi_polynomial_eval (poly, b1);
      if (y < min || max < y)
	break;
    }

    if (i == p->num_minmax) {
      double q = 1;
      while (1) {
	b0 = b1;
	b1 = b1 + q;
	q *= 2;
	y = guppi_polynomial_eval (poly, b1);
	if (y < min || max < y)
	  break;
      }
    }

    for (i=0; i<max_iter && b1 - b0 > POLYEPSILON; ++i) {
      double mid_x = (b0+b1)/2;
      y = guppi_polynomial_eval (poly, mid_x);
      
      if (min <= y && y <= max) {
	b0 = mid_x;
      } else {
	b1 = mid_x;
      }
    }

    *b = b0;

  }

  return TRUE;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

void
guppi_polynomial_spew (GuppiPolynomial *poly)
{
  GuppiPolynomialPrivate *p;
  gint i;

  g_return_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly));
  p = priv (poly);

  for (i = p->d; i >= 0; --i) {

    if (fabs (p->c[i]) > POLYEPSILON) {
      
      if (fabs (p->c[i] - 1) > POLYEPSILON || i == 0)
	g_print ("%g ", p->c[i]);

      if (i) {
	if (i == 1)
	  g_print ("x + ");
	else
	  g_print ("x^%d + ", i);
      }
    }
  }

  g_print ("\n");
}

xmlNodePtr
guppi_polynomial_export_xml (GuppiPolynomial *poly, GuppiXMLDocument *doc)
{
  GuppiPolynomialPrivate *p;
  xmlNodePtr poly_node;
  xmlNodePtr term_node;
  gint i;

  g_return_val_if_fail (poly && GUPPI_IS_POLYNOMIAL (poly), NULL);
  g_return_val_if_fail (doc != NULL, NULL);

  p = priv (poly);

  poly_node = guppi_xml_new_node (doc, "Polynomial");
  guppi_xml_set_propertyf (poly_node, "degree", "%d", p->d);
  if (p->num_roots >= 0)
    guppi_xml_set_propertyf (poly_node, "roots", "%d", p->num_roots);
  if (p->num_minmax >= 0)
    guppi_xml_set_propertyf (poly_node, "minmax", "%d", p->num_minmax);

  for (i = 0; i <= p->d; ++i) {
    if (fabs (p->c[i]) >= POLYEPSILON) {
      term_node = guppi_xml_new_text_nodef (doc, "term", "%g", p->c[i]);
      guppi_xml_set_propertyf (term_node, "degree", "%d", i);
      xmlAddChild (poly_node, term_node);
    }
  }

  for (i = 0; i < p->num_roots; ++i) {
    term_node = guppi_xml_new_text_nodef (doc, "root", "%g", p->roots[i]);
    xmlAddChild (poly_node, term_node);
  }

  for (i = 0; i < p->num_minmax; ++i) {
    term_node = guppi_xml_new_text_nodef (doc, "minmax", "%g", p->minmax[i]);
    xmlAddChild (poly_node, term_node);
  }

  return poly_node;
}

GuppiPolynomial *
guppi_polynomial_import_xml (GuppiXMLDocument *doc, xmlNodePtr node)
{
  GuppiPolynomial *poly;
  GuppiPolynomialPrivate *p;
  gint i, deg;
  gchar *buf;
  
  g_return_val_if_fail (doc != NULL, NULL);
  g_return_val_if_fail (node != NULL, NULL);

  if (strcmp (node->name, "Polynomial"))
    return NULL;

  buf = xmlGetProp (node, "degree");
  deg = buf ? atoi (buf) : 0;
  free (buf);

  poly = gtk_type_new (GUPPI_TYPE_POLYNOMIAL);
  p = priv (poly);

  guppi_polynomial_freeze (poly);

  guppi_polynomial_grow (poly, deg);

  node = node->xmlChildrenNode;

  while (node) {

    if (!strcmp (node->name, "term")) {

      buf = xmlGetProp (node, "degree");
      i = buf ? atoi (buf) : 0;
      free (buf);

      buf = xmlNodeListGetString (doc->doc, node->xmlChildrenNode, 1);
      if (0 <= i && i <= deg)
	p->c[i] = atof (buf);
      free (buf);
    }
    
    node = node->next;
  }

  guppi_polynomial_thaw (poly);

  return poly;
}



syntax highlighted by Code2HTML, v. 0.9.1