[gnumeric] Solver: code cleanup.



commit edd097dccbbaebce458d22c398ffd5e83d6ace3b
Author: Morten Welinder <terra gnome org>
Date:   Sun Apr 26 22:55:54 2015 -0400

    Solver: code cleanup.
    
    This creates object types for solver iterations and moves more
    generic iterative stuff into GnmIterSolver.

 NEWS                          |    1 +
 plugins/nlsolve/gnm-nlsolve.c |  281 ++++++++++++---------------------
 src/tools/gnm-solver.c        |  347 +++++++++++++++++++++++++++++++++++++----
 src/tools/gnm-solver.h        |   80 +++++++++-
 4 files changed, 495 insertions(+), 214 deletions(-)
---
diff --git a/NEWS b/NEWS
index 5a69b7e..006b596 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ Morten:
        * Fix export of unlabelled axes.
        * Fix export of rotated axis labels.
        * Fix xlsx save crash related to shared strings.  [#748477]
+       * Solver code refactoring.
 
 --------------------------------------------------------------------------
 Gnumeric 1.12.22
diff --git a/plugins/nlsolve/gnm-nlsolve.c b/plugins/nlsolve/gnm-nlsolve.c
index 97ba2fc..4d7d0b6 100644
--- a/plugins/nlsolve/gnm-nlsolve.c
+++ b/plugins/nlsolve/gnm-nlsolve.c
@@ -35,24 +35,17 @@
 /*
  * Note: the solver code assumes the problem is a minimization problem.
  * When used for a maximization problem, we flip the objective function
- * sign.  This is done in functions get_value and gnm_nlsolve_set_solution.
+ * sign.
  */
 
 
 typedef struct {
-       GnmIterSolver *parent;
+       /* The solver object in two forms.  */
+       GnmSolver *sol;
+       GnmIterSolver *isol;
 
-       /* Input/output cells.  */
-       GPtrArray *vars;
-       GnmCellPos origin;
-       int input_width, input_height;
-       gboolean maximize; /* See note above */
-
-       /* Initial point.  */
-       gnm_float *x0;
-
-       /* Current point.  */
-       gnm_float *xk, yk;
+       /* Number of vars.  */
+       int n;
 
        /* Rosenbrock state */
        gnm_float **xi;
@@ -65,26 +58,6 @@ typedef struct {
        gnm_float min_factor;
 } GnmNlsolve;
 
-static void free_matrix (gnm_float **m, int n);
-static void rosenbrock_tentative_end (GnmNlsolve *nl, gboolean accept);
-
-static void
-gnm_nlsolve_final (GnmNlsolve *nl)
-{
-       const int n = nl->vars->len;
-
-       rosenbrock_tentative_end (nl, FALSE);
-
-       g_free (nl->xk);
-       g_free (nl->x0);
-       if (nl->xi) {
-               free_matrix (nl->xi, n);
-               nl->xi = NULL;
-       }
-
-       g_free (nl);
-}
-
 static gboolean
 check_program (const GnmSolverParameters *params, GError **err)
 {
@@ -125,32 +98,21 @@ print_vector (const char *name, const gnm_float *v, int n)
 static void
 set_value (GnmNlsolve *nl, int i, gnm_float x)
 {
-       GnmCell *cell = g_ptr_array_index (nl->vars, i);
-       if (cell->value &&
-           VALUE_IS_FLOAT (cell->value) &&
-           value_get_as_float (cell->value) == x)
-               return;
-
-       gnm_cell_set_value (cell, value_new_float (x));
-       cell_queue_recalc (cell);
+       gnm_solver_set_var (nl->sol, i, x);
 }
 
 static void
 set_vector (GnmNlsolve *nl, const gnm_float *xs)
 {
-       const int n = nl->vars->len;
-       int i;
-
-       for (i = 0; i < n; i++)
-               set_value (nl, i, xs[i]);
+       gnm_solver_set_vars (nl->sol, xs);
 }
 
 /* Get the target value as-if we were minimizing.  */
 static gnm_float
 get_value (GnmNlsolve *nl)
 {
-       gnm_float y = gnm_solver_get_target_value (GNM_SOLVER (nl->parent));
-       return nl->maximize ? 0 - y : y;
+       /* nl->isol has been taught to flip sign if needed.  */
+       return gnm_iter_solver_get_target_value (nl->isol);
 }
 
 static void
@@ -163,60 +125,10 @@ free_matrix (gnm_float **m, int n)
 }
 
 static void
-gnm_nlsolve_set_solution (GnmNlsolve *nl)
+set_solution (GnmNlsolve *nl)
 {
-       GnmSolver *sol = GNM_SOLVER (nl->parent);
-       GnmSolverResult *result = g_object_new (GNM_SOLVER_RESULT_TYPE, NULL);
-       const int n = nl->vars->len;
-       int i;
-
-       result->quality = GNM_SOLVER_RESULT_FEASIBLE;
-       result->value = nl->maximize ? 0 - nl->yk : nl->yk;
-       result->solution = value_new_array_empty (nl->input_width,
-                                                 nl->input_height);
-       for (i = 0; i < n; i++) {
-               GnmCell *cell = g_ptr_array_index (nl->vars, i);
-               value_array_set (result->solution,
-                                cell->pos.col - nl->origin.col,
-                                cell->pos.row - nl->origin.row,
-                                value_new_float (nl->xk[i]));
-       }
-
-       g_object_set (sol, "result", result, NULL);
-       g_object_unref (result);
-
-       if (!gnm_solver_check_constraints (sol)) {
-               g_printerr ("Infeasible solution set\n");
-       }
-}
-
-static gboolean
-gnm_nlsolve_get_initial_solution (GnmNlsolve *nl, GError **err)
-{
-       GnmSolver *sol = GNM_SOLVER (nl->parent);
-       const int n = nl->vars->len;
-       int i;
-
-       if (gnm_solver_check_constraints (sol))
-               goto got_it;
-
-       /* More? */
-
-       g_set_error (err,
-                    go_error_invalid (),
-                    0,
-                    _("The initial values do not satisfy the constraints."));
-       return FALSE;
-
-got_it:
-       for (i = 0; i < n; i++) {
-               GnmCell *cell = g_ptr_array_index (nl->vars, i);
-               nl->xk[i] = nl->x0[i] = value_get_as_float (cell->value);
-       }
-       nl->yk = get_value (nl);
-       gnm_nlsolve_set_solution (nl);
-
-       return TRUE;
+       /* nl->isol has been taught to flip sign if needed.  */
+       gnm_iter_solver_set_solution (nl->isol);
 }
 
 static gboolean
@@ -231,7 +143,7 @@ gnm_nlsolve_prepare (GnmSolver *sol, WorkbookControl *wbc, GError **err,
 
        ok = check_program (sol->params, err);
        if (ok)
-               ok = gnm_nlsolve_get_initial_solution (nl, err);
+               ok = gnm_iter_solver_get_initial_solution (nl->isol, err);
 
        if (ok) {
                gnm_solver_set_status (sol, GNM_SOLVER_STATUS_PREPARED);
@@ -247,7 +159,7 @@ compute_gradient (GnmNlsolve *nl, const gnm_float *xs)
 {
        gnm_float *g;
        gnm_float y0;
-       const int n = nl->vars->len;
+       const int n = nl->n;
        int i;
 
        set_vector (nl, xs);
@@ -279,7 +191,7 @@ static gnm_float **
 compute_hessian (GnmNlsolve *nl, const gnm_float *xs, const gnm_float *g0)
 {
        gnm_float **H, *xs2;
-       const int n = nl->vars->len;
+       const int n = nl->n;
        int i, j;
 
        H = g_new (gnm_float *, n);
@@ -321,8 +233,8 @@ compute_hessian (GnmNlsolve *nl, const gnm_float *xs, const gnm_float *g0)
 static gboolean
 newton_improve (GnmNlsolve *nl, gnm_float *xs, gnm_float *y, gnm_float ymax)
 {
-       GnmSolver *sol = GNM_SOLVER (nl->parent);
-       const int n = nl->vars->len;
+       GnmSolver *sol = nl->sol;
+       const int n = nl->n;
        gnm_float *g, **H, *d;
        gboolean ok;
 
@@ -379,7 +291,7 @@ newton_improve (GnmNlsolve *nl, gnm_float *xs, gnm_float *y, gnm_float ymax)
 static void
 rosenbrock_init (GnmNlsolve *nl)
 {
-       const int n = nl->vars->len;
+       const int n = nl->n;
        int i, j;
 
        nl->xi = g_new (gnm_float *, n);
@@ -398,11 +310,12 @@ rosenbrock_init (GnmNlsolve *nl)
 static void
 rosenbrock_tentative_end (GnmNlsolve *nl, gboolean accept)
 {
-       const int n = nl->vars->len;
+       const int n = nl->n;
+       GnmIterSolver *isol = nl->isol;
 
        if (!accept && nl->tentative_xk) {
-               nl->yk = nl->tentative_yk;
-               memcpy (nl->xk, nl->tentative_xk, n * sizeof (gnm_float));
+               nl->isol->yk = nl->tentative_yk;
+               memcpy (isol->xk, nl->tentative_xk, n * sizeof (gnm_float));
        }
 
        nl->tentative = 0;
@@ -415,9 +328,9 @@ rosenbrock_tentative_end (GnmNlsolve *nl, gboolean accept)
 static gboolean
 rosenbrock_iter (GnmNlsolve *nl)
 {
-       GnmSolver *sol = GNM_SOLVER (nl->parent);
-       GnmIterSolver *isol = GNM_ITER_SOLVER (sol);
-       const int n = nl->vars->len;
+       GnmSolver *sol = nl->sol;
+       GnmIterSolver *isol = nl->isol;
+       const int n = nl->n;
        int i, j;
        const gnm_float alpha = 3;
        const gnm_float beta = 0.5;
@@ -425,7 +338,7 @@ rosenbrock_iter (GnmNlsolve *nl)
        gnm_float *d, **A, *x, *dx, *t;
        char *state;
        int dones = 0;
-       gnm_float ykm1 = nl->yk, *xkm1;
+       gnm_float ykm1 = isol->yk, *xkm1;
        gnm_float eps = gnm_pow2 (-16);
        int safety = 0;
 
@@ -457,12 +370,12 @@ rosenbrock_iter (GnmNlsolve *nl)
 
        d = g_new (gnm_float, n);
        for (i = 0; i < n; i++) {
-               d[i] = (nl->xk[i] == 0)
+               d[i] = (isol->xk[i] == 0)
                        ? eps
-                       : gnm_abs (nl->xk[i]) * eps;
+                       : gnm_abs (isol->xk[i]) * eps;
        }
 
-       xkm1 = g_memdup (nl->xk, n * sizeof (gnm_float));
+       xkm1 = g_memdup (isol->xk, n * sizeof (gnm_float));
 
        state = g_new0 (char, n);
 
@@ -482,15 +395,15 @@ rosenbrock_iter (GnmNlsolve *nl)
 
                        /* x = xk + (d[i] * xi[i])  */
                        for (j = 0; j < n; j++)
-                               x[j] = nl->xk[j] + d[i] * nl->xi[i][j];
+                               x[j] = isol->xk[j] + d[i] * nl->xi[i][j];
 
                        set_vector (nl, x);
                        y = get_value (nl);
 
-                       if (y <= nl->yk && gnm_solver_check_constraints (sol)) {
-                               if (y < nl->yk) {
-                                       nl->yk = y;
-                                       memcpy (nl->xk, x, n * sizeof (gnm_float));
+                       if (y <= isol->yk && gnm_solver_check_constraints (sol)) {
+                               if (y < isol->yk) {
+                                       isol->yk = y;
+                                       memcpy (isol->xk, x, n * sizeof (gnm_float));
                                        dx[i] += d[i];
                                        any_at_all = TRUE;
                                }
@@ -562,17 +475,17 @@ rosenbrock_iter (GnmNlsolve *nl)
                /* ---------------------------------------- */
 
                if (!nl->tentative) {
-                       set_vector (nl, nl->xk);
-                       gnm_nlsolve_set_solution (nl);
+                       set_vector (nl, isol->xk);
+                       set_solution (nl);
                }
 
                if (nl->tentative) {
-                       if (nl->yk < nl->tentative_yk) {
+                       if (isol->yk < nl->tentative_yk) {
                                if (nl->debug)
                                        g_printerr ("Tentative move accepted!\n");
                                rosenbrock_tentative_end (nl, TRUE);
                        }
-               } else if (gnm_abs (nl->yk - ykm1) > gnm_abs (ykm1) * 0.01) {
+               } else if (gnm_abs (isol->yk - ykm1) > gnm_abs (ykm1) * 0.01) {
                        /* A big step.  */
                        nl->smallsteps = 0;
                } else {
@@ -580,23 +493,23 @@ rosenbrock_iter (GnmNlsolve *nl)
                }
 
                if (!nl->tentative && nl->smallsteps > 50) {
-                       gnm_float yk = nl->yk;
+                       gnm_float yk = isol->yk;
 
                        nl->tentative = 10;
-                       nl->tentative_xk = g_memdup (nl->xk, n * sizeof (gnm_float));
+                       nl->tentative_xk = g_memdup (isol->xk, n * sizeof (gnm_float));
                        nl->tentative_yk = yk;
 
                        for (i = 0; i < 4; i++) {
                                gnm_float ymax = yk +
                                        gnm_abs (yk) * (0.10 / (i + 1));
                                if (i > 0)
-                                       ymax = MIN (ymax, nl->yk);
-                               if (!newton_improve (nl, nl->xk, &nl->yk, ymax))
+                                       ymax = MIN (ymax, isol->yk);
+                               if (!newton_improve (nl, isol->xk, &isol->yk, ymax))
                                        break;
                        }
 
                        if (nl->debug)
-                               print_vector ("Tentative move to", nl->xk, n);
+                               print_vector ("Tentative move to", isol->xk, n);
                }
        }
 
@@ -614,8 +527,9 @@ rosenbrock_iter (GnmNlsolve *nl)
 static gboolean
 polish_iter (GnmNlsolve *nl)
 {
-       GnmSolver *sol = GNM_SOLVER (nl->parent);
-       const int n = nl->vars->len;
+       GnmSolver *sol = nl->sol;
+       GnmIterSolver *isol = nl->isol;
+       const int n = nl->n;
        gnm_float *x;
        gnm_float step;
        gboolean any_at_all = FALSE;
@@ -627,19 +541,19 @@ polish_iter (GnmNlsolve *nl)
                for (c = 0; c < n; c++) {
                        for (s = 0; s <= 1; s++) {
                                gnm_float y;
-                               gnm_float dx = step * gnm_abs (nl->xk[c]);
+                               gnm_float dx = step * gnm_abs (isol->xk[c]);
 
                                if (dx == 0) dx = step;
                                if (s) dx = -dx;
 
-                               memcpy (x, nl->xk, n * sizeof (gnm_float));
+                               memcpy (x, isol->xk, n * sizeof (gnm_float));
                                x[c] += dx;
                                set_vector (nl, x);
                                y = get_value (nl);
 
-                               if (y < nl->yk && gnm_solver_check_constraints (sol))  {
-                                       nl->yk = y;
-                                       memcpy (nl->xk, x, n * sizeof (gnm_float));
+                               if (y < isol->yk && gnm_solver_check_constraints (sol))  {
+                                       isol->yk = y;
+                                       memcpy (isol->xk, x, n * sizeof (gnm_float));
                                        any_at_all = TRUE;
                                        if (nl->debug)
                                                g_printerr ("Polish step %.15" GNM_FORMAT_g
@@ -654,37 +568,56 @@ polish_iter (GnmNlsolve *nl)
        g_free (x);
 
        if (any_at_all)
-               gnm_nlsolve_set_solution (nl);
+               set_solution (nl);
 
        return any_at_all;
 }
 
 static gboolean
-gnm_nlsolve_iterate (GnmIterSolver *isol, GnmNlsolve *nl)
+gnm_nlsolve_iterate (GnmSolverIterator *iter, GnmNlsolve *nl)
 {
-       const int n = nl->vars->len;
-       gboolean progress;
+       GnmIterSolver *isol = nl->isol;
+       const int n = nl->n;
 
        if (isol->iterations == 0)
                rosenbrock_init (nl);
 
        if (nl->debug) {
                g_printerr ("Iteration %ld at %.15" GNM_FORMAT_g "\n",
-                           (long)(isol->iterations), nl->yk);
-               print_vector ("Current point", nl->xk, n);
+                           (long)(isol->iterations), isol->yk);
+               print_vector ("Current point", isol->xk, n);
        }
 
-       progress = rosenbrock_iter (nl);
+       return rosenbrock_iter (nl);
+}
+
+static gboolean
+gnm_nlsolve_polish (GnmSolverIterator *iter, GnmNlsolve *nl)
+{
+       return !nl->tentative && polish_iter (nl);
+}
+
+static void
+gnm_nlsolve_final (GnmNlsolve *nl)
+{
+       const int n = nl->n;
 
-       if (!progress && !nl->tentative) {
-               progress = polish_iter (nl);
+       /* Accept, i.e., don't try to restore.  */
+       rosenbrock_tentative_end (nl, TRUE);
+
+       if (nl->xi) {
+               free_matrix (nl->xi, n);
+               nl->xi = NULL;
        }
 
-       return progress;
+       g_free (nl);
 }
 
-gboolean
-nlsolve_solver_factory_functional (GnmSolverFactory *factory);
+/* ------------------------------------------------------------------------- */
+/* Plug-in interface.  */
+
+gboolean nlsolve_solver_factory_functional (GnmSolverFactory *factory);
+GnmSolver *nlsolve_solver_factory (GnmSolverFactory *factory, GnmSolverParameters *params);
 
 gboolean
 nlsolve_solver_factory_functional (GnmSolverFactory *factory)
@@ -692,49 +625,41 @@ nlsolve_solver_factory_functional (GnmSolverFactory *factory)
        return TRUE;
 }
 
-
-GnmSolver *
-nlsolve_solver_factory (GnmSolverFactory *factory, GnmSolverParameters *params);
-
 GnmSolver *
 nlsolve_solver_factory (GnmSolverFactory *factory, GnmSolverParameters *params)
 {
-       GnmIterSolver *res = g_object_new (GNM_ITER_SOLVER_TYPE,
-                                          "params", params,
-                                          NULL);
+       GnmIterSolver *res = g_object_new
+               (GNM_ITER_SOLVER_TYPE,
+                "params", params,
+                "flip-sign", (params->problem_type == GNM_SOLVER_MAXIMIZE),
+                NULL);
        GnmNlsolve *nl = g_new0 (GnmNlsolve, 1);
-       int n;
-       GnmValue const *vinput = gnm_solver_param_get_input (params);
-       GnmEvalPos ep;
-       GnmCellRef origin;
+       GnmSolverIteratorCompound *citer;
+       GnmSolverIterator *iter;
 
-       nl->parent = res;
+       citer = g_object_new (GNM_SOLVER_ITERATOR_COMPOUND_TYPE, NULL);
 
-       nl->maximize = (params->problem_type == GNM_SOLVER_MAXIMIZE);
+       iter = gnm_solver_iterator_new_func (G_CALLBACK (gnm_nlsolve_iterate), nl);
+       gnm_solver_iterator_compound_add (citer, iter, 1);
 
-       eval_pos_init_sheet (&ep, params->sheet);
-       if (vinput) {
-               gnm_cellref_make_abs (&origin, &vinput->v_range.cell.a, &ep);
-               nl->origin.col = origin.col;
-               nl->origin.row = origin.row;
-               nl->input_width = value_area_get_width (vinput, &ep);
-               nl->input_height = value_area_get_height (vinput, &ep);
-       }
+       iter = gnm_solver_iterator_new_func (G_CALLBACK (gnm_nlsolve_polish), nl);
+       gnm_solver_iterator_compound_add (citer, iter, 0);
 
-       nl->debug = gnm_solver_debug ();
-       nl->min_factor = 1e-10;
+       res->iterator = GNM_SOLVER_ITERATOR (citer);
 
-       nl->vars = GNM_SOLVER (res)->input_cells;
-       n = nl->vars->len;
+       nl->sol = GNM_SOLVER (res);
+       nl->isol = res;
 
-       nl->x0 = g_new (gnm_float, n);
-       nl->xk = g_new (gnm_float, n);
+       nl->debug = gnm_solver_debug ();
+       nl->min_factor = 1e-10;
+       nl->n = nl->sol->input_cells->len;
 
        g_signal_connect (res, "prepare", G_CALLBACK (gnm_nlsolve_prepare), nl);
-       g_signal_connect (res, "iterate", G_CALLBACK (gnm_nlsolve_iterate), nl);
 
        g_object_set_data_full (G_OBJECT (res), PRIVATE_KEY, nl,
                                (GDestroyNotify)gnm_nlsolve_final);
 
        return GNM_SOLVER (res);
 }
+
+/* ------------------------------------------------------------------------- */
diff --git a/src/tools/gnm-solver.c b/src/tools/gnm-solver.c
index 21cb8dd..70504fc 100644
--- a/src/tools/gnm-solver.c
+++ b/src/tools/gnm-solver.c
@@ -824,9 +824,22 @@ gnm_solver_constructed (GObject *obj)
 {
        GnmSolver *sol = GNM_SOLVER (obj);
        GnmSolverParameters *params = sol->params;
+       GnmValue const *vinput = gnm_solver_param_get_input (params);
 
        sol->target = gnm_solver_param_get_target_cell (params);
+
        sol->input_cells = gnm_solver_param_get_input_cells (params);
+       if (vinput) {
+               GnmCellRef origin;
+               GnmEvalPos ep;
+
+               eval_pos_init_sheet (&ep, params->sheet);
+               gnm_cellref_make_abs (&origin, &vinput->v_range.cell.a, &ep);
+               sol->origin.col = origin.col;
+               sol->origin.row = origin.row;
+               sol->input_width = value_area_get_width (vinput, &ep);
+               sol->input_height = value_area_get_height (vinput, &ep);
+       }
 
        gnm_solver_parent_class->constructed (obj);
 }
@@ -1034,8 +1047,8 @@ gnm_solver_store_result (GnmSolver *sol)
 {
        GnmValue const *vinput;
        GnmSheetRange sr;
-       int h, w, x, y;
        GnmValue const *solution;
+       unsigned ui, n = sol->input_cells->len;
 
        g_return_if_fail (GNM_IS_SOLVER (sol));
        g_return_if_fail (sol->result != NULL);
@@ -1044,25 +1057,20 @@ gnm_solver_store_result (GnmSolver *sol)
        vinput = gnm_solver_param_get_input (sol->params);
        gnm_sheet_range_from_value (&sr, vinput);
        if (!sr.sheet) sr.sheet = sol->params->sheet;
-       h = range_height (&sr.range);
-       w = range_width (&sr.range);
 
        solution = gnm_solver_has_solution (sol)
                ? sol->result->solution
                : NULL;
 
-       for (x = 0; x < w; x++) {
-               for (y = 0; y < h; y++) {
-                       GnmValue *v = solution
-                               ? value_dup (value_area_fetch_x_y (solution, x, y, NULL))
-                               : value_new_error_NA (NULL);
-                       GnmCell *cell =
-                               sheet_cell_fetch (sr.sheet,
-                                                 sr.range.start.col + x,
-                                                 sr.range.start.row + y);
-                       gnm_cell_set_value (cell, v);
-                       cell_queue_recalc (cell);
-               }
+       for (ui = 0; ui < n; ui++) {
+               GnmCell *cell = g_ptr_array_index (sol->input_cells, ui);
+               int x = cell->pos.col - sol->origin.col;
+               int y = cell->pos.row - sol->origin.row;
+               GnmValue *v = solution
+                       ? value_dup (value_area_fetch_x_y (solution, x, y, NULL))
+                       : value_new_error_NA (NULL);
+               gnm_cell_set_value (cell, v);
+               cell_queue_recalc (cell);
        }
 }
 
@@ -1293,7 +1301,7 @@ cell_in_cr (GnmCell const *cell, GnmSheetRange *sr, gboolean follow,
 
        if (sr->sheet != cell->base.sheet ||
            !range_contains (&sr->range, cell->pos.col, cell->pos.row)) {
-               /* If the expression is just =X42 thenm look at X42 instead.
+               /* If the expression is just =X42 then look at X42 instead.
                   This is because the mps loader uses such a level of
                   indirection.  Note: we follow only one such step.  */
                GnmCellRef const *cr = gnm_expr_top_get_cellref (cell->base.texpr);
@@ -1671,6 +1679,30 @@ gnm_solver_get_target_value (GnmSolver *solver)
                return gnm_nan;
 }
 
+void
+gnm_solver_set_var (GnmSolver *sol, int i, gnm_float x)
+{
+       GnmCell *cell = g_ptr_array_index (sol->input_cells, i);
+
+       if (cell->value &&
+           VALUE_IS_FLOAT (cell->value) &&
+           value_get_as_float (cell->value) == x)
+               return;
+
+       gnm_cell_set_value (cell, value_new_float (x));
+       cell_queue_recalc (cell);
+}
+
+void
+gnm_solver_set_vars (GnmSolver *sol, gnm_float const *xs)
+{
+       const int n = sol->input_cells->len;
+       int i;
+
+       for (i = 0; i < n; i++)
+               gnm_solver_set_var (sol, i, xs[i]);
+}
+
 static void
 gnm_solver_class_init (GObjectClass *object_class)
 {
@@ -2157,14 +2189,222 @@ GSF_CLASS (GnmSubSolver, gnm_sub_solver,
 
 /* ------------------------------------------------------------------------- */
 
+enum {
+       SOL_ITER_SIG_ITERATE,
+       SOL_ITER_SIG_LAST
+};
+
+static guint solver_iterator_signals[SOL_ITER_SIG_LAST] = { 0 };
+
+static void
+gnm_solver_iterator_class_init (GObjectClass *object_class)
+{
+       solver_iterator_signals[SOL_ITER_SIG_ITERATE] =
+               g_signal_new ("iterate",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GnmSolverIteratorClass, iterate),
+                             NULL, NULL,
+                             gnm__BOOLEAN__VOID,
+                             G_TYPE_BOOLEAN, 0);
+}
+
+gboolean
+gnm_solver_iterator_iterate (GnmSolverIterator *iter)
+{
+       gboolean progress = FALSE;
+       g_signal_emit (iter, solver_iterator_signals[SOL_ITER_SIG_ITERATE], 0, &progress);
+       return progress;
+}
+
+GnmSolverIterator *
+gnm_solver_iterator_new_func (GCallback iterate, gpointer user)
+{
+       GnmSolverIterator *iter;
+
+       iter = g_object_new (GNM_SOLVER_ITERATOR_TYPE, NULL);
+       g_signal_connect (iter, "iterate", G_CALLBACK (iterate), user);
+       return iter;
+}
+
+GSF_CLASS (GnmSolverIterator, gnm_solver_iterator,
+          gnm_solver_iterator_class_init, NULL, G_TYPE_OBJECT)
+
+/* ------------------------------------------------------------------------- */
+
+static GObjectClass *gnm_solver_iterator_compound_parent_class;
+
+/**
+ * gnm_solver_iterator_compound_add:
+ * @ic: Compound iterator
+ * @iter: (transfer full): sub-iterator
+ * @count: repeat count
+ *
+ * Add an iterator to a compound iterator with a given repeat count.  As a
+ * special case, a repeat count of zero means to try the iterator once
+ * in a cycle, but only if no other sub-iterator has shown any progress so far.
+ */
+void
+gnm_solver_iterator_compound_add (GnmSolverIteratorCompound *ic,
+                                 GnmSolverIterator *iter,
+                                 unsigned count)
+{
+       g_ptr_array_add (ic->iterators, iter);
+       ic->counts = g_renew (unsigned, ic->counts, ic->iterators->len);
+       ic->counts[ic->iterators->len - 1] = count;
+}
+
+static gboolean
+gnm_solver_iterator_compound_iterate (GnmSolverIterator *iter)
+{
+       GnmSolverIteratorCompound *ic = (GnmSolverIteratorCompound *)iter;
+       gboolean progress;
+
+       while (TRUE) {
+               if (ic->cycle >= ic->cycles)
+                       return FALSE;
+
+               if (ic->next >= ic->iterators->len) {
+                       /* We've been through all iterators.  */
+                       if (!ic->cycle_progress)
+                               return FALSE;
+                       ic->cycle_progress = FALSE;
+                       ic->next = 0;
+                       ic->next_counter = 0;
+                       ic->cycle++;
+                       continue;
+               }
+
+               if (ic->next_counter < ic->counts[ic->next])
+                       break;
+
+               /* Special case: when count==0, use only if no progress.  */
+               if (!ic->cycle_progress && ic->next_counter == 0)
+                       break;
+
+               ic->next++;
+               ic->next_counter = 0;
+       }
+
+       progress = gnm_solver_iterator_iterate (g_ptr_array_index (ic->iterators, ic->next));
+       if (progress) {
+               ic->cycle_progress = TRUE;
+               ic->next_counter++;
+       } else {
+               /* No progress, so don't retry.  */
+               ic->next++;
+               ic->next_counter = 0;
+       }
+
+       /* Report progress as long as we have stuff to try.  */
+       return TRUE;
+}
+
+static void
+gnm_solver_iterator_compound_init (GnmSolverIteratorCompound *ic)
+{
+       ic->iterators = g_ptr_array_new ();
+       ic->cycles = G_MAXUINT;
+}
+
+static void
+gnm_solver_iterator_compound_finalize (GObject *obj)
+{
+       GnmSolverIteratorCompound *ic = (GnmSolverIteratorCompound *)obj;
+       g_ptr_array_foreach (ic->iterators, (GFunc)g_object_unref, NULL);
+       g_ptr_array_free (ic->iterators, TRUE);
+       g_free (ic->counts);
+       gnm_solver_iterator_compound_parent_class->finalize (obj);
+}
+
+static void
+gnm_solver_iterator_compound_class_init (GObjectClass *object_class)
+{
+       GnmSolverIteratorClass *iclass = (GnmSolverIteratorClass *)object_class;
+
+       gnm_solver_iterator_compound_parent_class = g_type_class_peek_parent (object_class);
+
+       object_class->finalize = gnm_solver_iterator_compound_finalize;
+       iclass->iterate = gnm_solver_iterator_compound_iterate;
+}
+
+GSF_CLASS (GnmSolverIteratorCompound, gnm_solver_iterator_compound,
+          gnm_solver_iterator_compound_class_init, gnm_solver_iterator_compound_init, 
GNM_SOLVER_ITERATOR_TYPE)
+
+/* ------------------------------------------------------------------------- */
+
 static GObjectClass *gnm_iter_solver_parent_class;
 
 enum {
-       ITER_SOL_SIG_ITERATE,
-       ITER_SOL_SIG_LAST
+       ISOL_PROP_0,
+       ISOL_PROP_FLIP_SIGN
 };
 
-static guint iter_solver_signals[ITER_SOL_SIG_LAST] = { 0 };
+gnm_float
+gnm_iter_solver_get_target_value (GnmIterSolver *isol)
+{
+       GnmSolver *sol = GNM_SOLVER (isol);
+       gnm_float y = gnm_solver_get_target_value (sol);
+       return isol->flip_sign ? 0 - y : y;
+}
+
+gboolean
+gnm_iter_solver_get_initial_solution (GnmIterSolver *isol, GError **err)
+{
+       GnmSolver *sol = GNM_SOLVER (isol);
+       const int n = sol->input_cells->len;
+       int i;
+
+       if (gnm_solver_check_constraints (sol))
+               goto got_it;
+
+       /* More? */
+
+       g_set_error (err,
+                    go_error_invalid (),
+                    0,
+                    _("The initial values do not satisfy the constraints."));
+       return FALSE;
+
+got_it:
+       for (i = 0; i < n; i++) {
+               GnmCell *cell = g_ptr_array_index (sol->input_cells, i);
+               isol->xk[i] = value_get_as_float (cell->value);
+       }
+       isol->yk = gnm_iter_solver_get_target_value (isol);
+
+       gnm_iter_solver_set_solution (isol);
+
+       return TRUE;
+}
+
+void
+gnm_iter_solver_set_solution (GnmIterSolver *isol)
+{
+       GnmSolver *sol = GNM_SOLVER (isol);
+       GnmSolverResult *result = g_object_new (GNM_SOLVER_RESULT_TYPE, NULL);
+       const int n = sol->input_cells->len;
+       int i;
+
+       result->quality = GNM_SOLVER_RESULT_FEASIBLE;
+       result->value = isol->flip_sign ? 0 - isol->yk : isol->yk;
+       result->solution = value_new_array_empty (sol->input_width,
+                                                 sol->input_height);
+       for (i = 0; i < n; i++) {
+               GnmCell *cell = g_ptr_array_index (sol->input_cells, i);
+               value_array_set (result->solution,
+                                cell->pos.col - sol->origin.col,
+                                cell->pos.row - sol->origin.row,
+                                value_new_float (isol->xk[i]));
+       }
+
+       g_object_set (sol, "result", result, NULL);
+       g_object_unref (result);
+
+       if (!gnm_solver_check_constraints (sol)) {
+               g_printerr ("Infeasible solution set\n");
+       }
+}
 
 static void
 gnm_iter_solver_clear (GnmIterSolver *isol)
@@ -2187,15 +2427,61 @@ static void
 gnm_iter_solver_finalize (GObject *obj)
 {
        GnmIterSolver *isol = GNM_ITER_SOLVER (obj);
-       (void)isol;
+       g_free (isol->xk);
        gnm_iter_solver_parent_class->finalize (obj);
 }
 
 static void
+gnm_iter_solver_constructed (GObject *obj)
+{
+       GnmIterSolver *isol = GNM_ITER_SOLVER (obj);
+       GnmSolver *sol = GNM_SOLVER (obj);
+
+       /* Chain to parent first */
+       gnm_iter_solver_parent_class->constructed (obj);
+
+       isol->xk = g_new0 (gnm_float, sol->input_cells->len);
+}
+
+static void
 gnm_iter_solver_init (GnmIterSolver *isol)
 {
 }
 
+static void
+gnm_iter_solver_get_property (GObject *object, guint property_id,
+                             GValue *value, GParamSpec *pspec)
+{
+       GnmIterSolver *isol = (GnmIterSolver *)object;
+
+       switch (property_id) {
+       case ISOL_PROP_FLIP_SIGN:
+               g_value_set_boolean (value, isol->flip_sign);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+gnm_iter_solver_set_property (GObject *object, guint property_id,
+                             GValue const *value, GParamSpec *pspec)
+{
+       GnmIterSolver *isol = (GnmIterSolver *)object;
+
+       switch (property_id) {
+       case ISOL_PROP_FLIP_SIGN:
+               isol->flip_sign = g_value_get_boolean (value);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
 static gint
 gnm_iter_solver_idle (gpointer data)
 {
@@ -2204,7 +2490,7 @@ gnm_iter_solver_idle (gpointer data)
        GnmSolverParameters *params = sol->params;
        gboolean progress;
 
-       g_signal_emit (isol, iter_solver_signals[ITER_SOL_SIG_ITERATE], 0, &progress);
+       progress = isol->iterator && gnm_solver_iterator_iterate (isol->iterator);
        isol->iterations++;
 
        if (!gnm_solver_finished (sol) &&
@@ -2244,6 +2530,8 @@ gnm_iter_solver_stop (GnmSolver *solver, GError **err)
 
        gnm_iter_solver_clear (isol);
 
+       g_clear_object (&isol->iterator);
+
        gnm_solver_set_status (sol, GNM_SOLVER_STATUS_CANCELLED);
 
        return TRUE;
@@ -2258,17 +2546,18 @@ gnm_iter_solver_class_init (GObjectClass *object_class)
 
        object_class->dispose = gnm_iter_solver_dispose;
        object_class->finalize = gnm_iter_solver_finalize;
+       object_class->constructed = gnm_iter_solver_constructed;
+       object_class->set_property = gnm_iter_solver_set_property;
+       object_class->get_property = gnm_iter_solver_get_property;
        sclass->start = gnm_iter_solver_start;
        sclass->stop = gnm_iter_solver_stop;
 
-       iter_solver_signals[ITER_SOL_SIG_ITERATE] =
-               g_signal_new ("iterate",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (GnmIterSolverClass, iterate),
-                             NULL, NULL,
-                             gnm__BOOLEAN__VOID,
-                             G_TYPE_BOOLEAN, 0);
+       g_object_class_install_property (object_class, ISOL_PROP_FLIP_SIGN,
+               g_param_spec_boolean ("flip-sign",
+                                     P_("Flip Sign"),
+                                     P_("Flip sign of target value"),
+                                     FALSE,
+                                     GSF_PARAM_STATIC | G_PARAM_READWRITE));
 }
 
 GSF_CLASS (GnmIterSolver, gnm_iter_solver,
diff --git a/src/tools/gnm-solver.h b/src/tools/gnm-solver.h
index d1b627d..abdaa52 100644
--- a/src/tools/gnm-solver.h
+++ b/src/tools/gnm-solver.h
@@ -204,6 +204,8 @@ typedef struct {
        /* Derived information */
        GnmCell *target;
        GPtrArray *input_cells;
+       GnmCellPos origin;
+       int input_width, input_height;
 } GnmSolver;
 
 typedef struct {
@@ -216,7 +218,7 @@ typedef struct {
        gboolean (*stop) (GnmSolver *solver, GError **err);
 } GnmSolverClass;
 
-GType gnm_solver_get_type  (void);
+GType gnm_solver_get_type (void);
 
 gboolean gnm_solver_prepare (GnmSolver *solver,
                             WorkbookControl *wbc, GError **err);
@@ -250,6 +252,8 @@ gboolean gnm_solver_saveas (GnmSolver *solver, WorkbookControl *wbc,
 gboolean gnm_solver_debug (void);
 
 gnm_float gnm_solver_get_target_value (GnmSolver *solver);
+void gnm_solver_set_var (GnmSolver *sol, int i, gnm_float x);
+void gnm_solver_set_vars (GnmSolver *sol, gnm_float const *xs);
 
 /* ------------------------------------------------------------------------- */
 /* Solver subclass for subprocesses. */
@@ -283,7 +287,7 @@ typedef struct {
        void (*child_exit) (GnmSubSolver *subsol, gboolean normal, int code);
 } GnmSubSolverClass;
 
-GType gnm_sub_solver_get_type  (void);
+GType gnm_sub_solver_get_type (void);
 
 void gnm_sub_solver_clear (GnmSubSolver *subsol);
 
@@ -309,26 +313,88 @@ char *gnm_sub_solver_locate_binary (const char *binary, const char *solver,
                                    WBCGtk *wbcg);
 
 /* ------------------------------------------------------------------------- */
+
+typedef struct GnmIterSolver_ GnmIterSolver;
+typedef struct GnmSolverIterator_ GnmSolverIterator;
+
+/* Utility class for single iteration in a solving process.  */
+
+#define GNM_SOLVER_ITERATOR_TYPE     (gnm_solver_iterator_get_type ())
+#define GNM_SOLVER_ITERATOR(o)       (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SOLVER_ITERATOR_TYPE, 
GnmSolverIterator))
+#define GNM_IS_SOLVER_ITERATOR(o)    (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_SOLVER_ITERATOR_TYPE))
+
+struct GnmSolverIterator_ {
+       GObject parent;
+};
+
+typedef struct {
+       GObjectClass parent_class;
+
+       gboolean (*iterate) (GnmSolverIterator *iter);
+} GnmSolverIteratorClass;
+
+GType gnm_solver_iterator_get_type (void);
+GnmSolverIterator *gnm_solver_iterator_new_func (GCallback iterate, gpointer user);
+gboolean gnm_solver_iterator_iterate (GnmSolverIterator *iter);
+
+
+
+#define GNM_SOLVER_ITERATOR_COMPOUND_TYPE     (gnm_solver_iterator_compound_get_type ())
+#define GNM_SOLVER_ITERATOR_COMPOUND(o)       (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GNM_SOLVER_ITERATOR_COMPOUND_TYPE, GnmSolverIteratorCompound))
+#define GNM_IS_SOLVER_ITERATOR_COMPOUND(o)    (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GNM_SOLVER_ITERATOR_COMPOUND_TYPE))
+
+typedef struct {
+       GnmSolverIterator parent;
+       unsigned cycles;
+
+       /* <protected> */
+       GPtrArray *iterators;
+       unsigned *counts;
+       unsigned next, next_counter, cycle;
+       gboolean cycle_progress;
+} GnmSolverIteratorCompound;
+
+typedef struct {
+       GnmSolverIteratorClass parent_class;
+} GnmSolverIteratorCompoundClass;
+
+GType gnm_solver_iterator_compound_get_type (void);
+void gnm_solver_iterator_compound_add (GnmSolverIteratorCompound *ic,
+                                      GnmSolverIterator *iter,
+                                      unsigned count);
+
+/* ------------------------------------------------------------------------- */
 /* Solver subclass for iterative in-process solvers. */
 
 #define GNM_ITER_SOLVER_TYPE     (gnm_iter_solver_get_type ())
 #define GNM_ITER_SOLVER(o)       (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_ITER_SOLVER_TYPE, GnmIterSolver))
 #define GNM_IS_ITER_SOLVER(o)    (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_ITER_SOLVER_TYPE))
 
-typedef struct {
+struct GnmIterSolver_ {
        GnmSolver parent;
 
+       /* Current point */
+       gnm_float *xk, yk;
+       gboolean flip_sign;
+
+       GnmSolverIterator *iterator;
+
        guint64 iterations;
+
+       /* <private> */
        guint idle_tag;
-} GnmIterSolver;
+};
 
 typedef struct {
        GnmSolverClass parent_class;
-
-       void (*iterate) (GnmIterSolver *isol);
 } GnmIterSolverClass;
 
-GType gnm_iter_solver_get_type  (void);
+GType gnm_iter_solver_get_type (void);
+
+gnm_float gnm_iter_solver_get_target_value (GnmIterSolver *isol);
+
+gboolean gnm_iter_solver_get_initial_solution (GnmIterSolver *isol, GError **err);
+void gnm_iter_solver_set_solution (GnmIterSolver *isol);
 
 /* ------------------------------------------------------------------------- */
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]