[gnumeric] solver: add (external) glpk solver.



commit 6100df1abebc439e6e679611176c2a80a9af0f34
Author: Morten Welinder <terra gnome org>
Date:   Sat Nov 14 12:59:22 2009 -0500

    solver: add (external) glpk solver.

 configure.in                    |    1 +
 plugins/Makefile.am             |    2 +-
 plugins/glpk/.gitignore         |    9 +
 plugins/glpk/ChangeLog          |    3 +
 plugins/glpk/Makefile.am        |   21 +++
 plugins/glpk/boot.c             |   24 +++
 plugins/glpk/boot.h             |   12 ++
 plugins/glpk/glpk-write.c       |  371 +++++++++++++++++++++++++++++++++++++++
 plugins/glpk/gnm-glpk.c         |  293 ++++++++++++++++++++++++++++++
 plugins/glpk/plugin.xml.in      |   29 +++
 plugins/lpsolve/gnm-lpsolve.c   |   12 ++-
 plugins/lpsolve/lpsolve-write.c |    6 +-
 plugins/lpsolve/plugin.xml.in   |    2 +-
 po-functions/POTFILES.in        |    4 +
 po-functions/POTFILES.skip      |    1 +
 po/POTFILES.in                  |    5 +
 src/dialogs/dialog-solver.c     |    2 +
 src/tools/gnm-solver.c          |   73 +++++++-
 src/tools/gnm-solver.h          |   11 ++
 19 files changed, 872 insertions(+), 9 deletions(-)
---
diff --git a/configure.in b/configure.in
index 66ff91e..c06b34f 100644
--- a/configure.in
+++ b/configure.in
@@ -1065,6 +1065,7 @@ plugins/gnome-glossary/Makefile
 plugins/html/Makefile
 plugins/lotus-123/Makefile
 plugins/lpsolve/Makefile
+plugins/glpk/Makefile
 plugins/mps/Makefile
 plugins/oleo/Makefile
 plugins/openoffice/Makefile
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 062da59..8f0dea1 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -1,5 +1,5 @@
 SUBDIRS_FILE_FORMATS = excel lotus-123 oleo sc sylk xbase html dif qpro \
-	plan-perfect applix openoffice lpsolve
+	plan-perfect applix openoffice lpsolve glpk
 
 if ENABLE_SOLVER
   SUBDIRS_FILE_FORMATS += mps
diff --git a/plugins/glpk/.gitignore b/plugins/glpk/.gitignore
new file mode 100644
index 0000000..6429675
--- /dev/null
+++ b/plugins/glpk/.gitignore
@@ -0,0 +1,9 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.lo
+*.la
+plugin.xml
+*.loT
+*.sw*
diff --git a/plugins/glpk/ChangeLog b/plugins/glpk/ChangeLog
new file mode 100644
index 0000000..33314bf
--- /dev/null
+++ b/plugins/glpk/ChangeLog
@@ -0,0 +1,3 @@
+2009-11-13  Morten Welinder  <terra gnome org>
+
+	* Initial Implementation
diff --git a/plugins/glpk/Makefile.am b/plugins/glpk/Makefile.am
new file mode 100644
index 0000000..8c66c24
--- /dev/null
+++ b/plugins/glpk/Makefile.am
@@ -0,0 +1,21 @@
+AM_CPPFLAGS = \
+    -DGNOMELOCALEDIR=\""$(datadir)/locale"\" 	\
+    -I$(top_srcdir)/src	-I$(top_builddir)/src	\
+    $(GNUMERIC_CFLAGS)
+
+gnumeric_plugin_glpkdir = $(gnumeric_plugindir)/glpk
+xmldir = $(gnumeric_plugin_glpkdir)
+gnumeric_plugin_glpk_LTLIBRARIES = glpk.la
+glpk_la_LDFLAGS = -module $(GNUMERIC_PLUGIN_LDFLAGS)
+glpk_la_SOURCES = \
+	boot.h boot.c \
+	gnm-glpk.c \
+	glpk-write.c
+
+xml_in_files = plugin.xml.in
+xml_DATA = $(xml_in_files:.xml.in=.xml)
+
+ INTLTOOL_XML_RULE@
+
+EXTRA_DIST = ChangeLog $(xml_in_files)
+DISTCLEANFILES = $(xml_DATA)
diff --git a/plugins/glpk/boot.c b/plugins/glpk/boot.c
new file mode 100644
index 0000000..fef583f
--- /dev/null
+++ b/plugins/glpk/boot.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009 Morten Welinder (terra gnome 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <gnumeric-config.h>
+#include <gnumeric.h>
+#include "boot.h"
+#include <gnm-plugin.h>
+
+GNM_PLUGIN_MODULE_HEADER;
diff --git a/plugins/glpk/boot.h b/plugins/glpk/boot.h
new file mode 100644
index 0000000..a40468a
--- /dev/null
+++ b/plugins/glpk/boot.h
@@ -0,0 +1,12 @@
+#ifndef GNUMERIC_GLPK_BOOT_H
+#define GNUMERIC_GLPK_BOOT_H
+
+#include "gnumeric.h"
+#include <goffice/goffice.h>
+#include <gsf/gsf-output.h>
+
+void
+glpk_file_save (GOFileSaver const *fs, GOIOContext *io_context,
+		WorkbookView const *wb_view, GsfOutput *output);
+
+#endif
diff --git a/plugins/glpk/glpk-write.c b/plugins/glpk/glpk-write.c
new file mode 100644
index 0000000..d4d81a3
--- /dev/null
+++ b/plugins/glpk/glpk-write.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2009 Morten Welinder (terra gnome 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <gnumeric-config.h>
+#include <boot.h>
+#include <numbers.h>
+#include <workbook-view.h>
+#include <sheet.h>
+#include <workbook.h>
+#include <value.h>
+#include <cell.h>
+#include <expr.h>
+#include <tools/gnm-solver.h>
+#include <ranges.h>
+#include <parse-util.h>
+#include <gutils.h>
+#include <goffice/goffice.h>
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+
+
+static gboolean
+gnm_solver_get_lp_coeff (GnmCell *target, GnmCell *cell,
+			 gnm_float *x, GError **err)
+{
+        gnm_float x0, x1;
+	gboolean res = FALSE;
+
+	if (!VALUE_IS_NUMBER (target->value))
+		goto fail;
+	x0 = value_get_as_float (target->value);
+
+	gnm_cell_set_value (cell, value_new_float (1));
+	cell_queue_recalc (cell);
+	gnm_cell_eval (target);
+	if (!VALUE_IS_NUMBER (target->value))
+		goto fail;
+	x1 = value_get_as_float (target->value);
+
+	*x = x1 - x0;
+	res = TRUE;
+	goto out;
+
+fail:
+	g_set_error (err,
+		     go_error_invalid (),
+		     0,
+		     _("Target cell did not evaluate to a number."));
+	*x = 0;
+
+out:
+	gnm_cell_set_value (cell, value_new_int (0));
+	cell_queue_recalc (cell);
+	gnm_cell_eval (target);
+
+	return res;
+}
+
+/*
+ * FIXME: we need to handle the situation where cells from more than one
+ * sheet are involved.
+ */
+static const char *
+glpk_var_name (GnmSubSolver *ssol, GnmCell const *cell)
+{
+	if (ssol)
+		return gnm_sub_solver_get_cell_name (ssol, cell);
+	return cell_name (cell);
+}
+
+static gboolean
+glpk_affine_func (GString *dst, GnmCell *target, GnmSubSolver *ssol,
+		  gboolean zero_too,
+		  gnm_float cst, GSList *input_cells, GError **err)
+{
+	GSList *l, *ol;
+	gboolean any = FALSE;
+	gnm_float y;
+	GSList *old_values = NULL;
+	gboolean ok = TRUE;
+
+	if (!target) {
+		gnm_string_add_number (dst, cst);
+		return TRUE;
+	}
+
+ 	for (l = input_cells; l; l = l->next) {
+	        GnmCell *cell = l->data;
+		old_values = g_slist_prepend (old_values,
+					      value_dup (cell->value));
+		gnm_cell_set_value (cell, value_new_int (0));
+		cell_queue_recalc (cell);
+	}
+	old_values = g_slist_reverse (old_values);
+
+	gnm_cell_eval (target);
+	y = cst + value_get_as_float (target->value);
+
+ 	for (l = input_cells; l; l = l->next) {
+	        GnmCell *cell = l->data;
+		gnm_float x;
+		ok = gnm_solver_get_lp_coeff (target, cell, &x, err);
+		if (!ok)
+			goto fail;
+		if (x == 0 && !zero_too)
+			continue;
+
+		if (any) {
+			if (x < 0)
+				g_string_append (dst, " - ");
+			else
+				g_string_append (dst, " + ");
+		} else {
+			if (x < 0)
+				g_string_append_c (dst, '-');
+		}
+		x = gnm_abs (x);
+
+		if (x != 1) {
+			gnm_string_add_number (dst, x);
+			g_string_append_c (dst, ' ');
+		}
+
+		g_string_append (dst, glpk_var_name (ssol, cell));
+
+		any = TRUE;
+	}
+
+	if (!any || y)
+		gnm_string_add_number (dst, y);
+
+fail:
+ 	for (l = input_cells, ol = old_values;
+	     l;
+	     l = l->next, ol = ol->next) {
+	        GnmCell *cell = l->data;
+		GnmValue *old = ol->data;
+		gnm_cell_set_value (cell, old);
+		cell_queue_recalc (cell);
+	}
+	g_slist_free (old_values);
+
+	return ok;
+}
+
+static GString *
+glpk_create_program (Sheet *sheet, GOIOContext *io_context,
+		     GnmSubSolver *ssol, GError **err)
+{
+	GnmSolverParameters *sp = sheet->solver_parameters;
+	GString *prg = NULL;
+	GString *constraints = g_string_new (NULL);
+	GString *binaries = g_string_new (NULL);
+	GString *integers = g_string_new (NULL);
+	GString *objfunc = g_string_new (NULL);
+	GSList *l;
+	GnmCell *target_cell = gnm_solver_param_get_target_cell (sp);
+	GSList *input_cells = gnm_solver_param_get_input_cells (sp);
+	gsize progress;
+
+	/* ---------------------------------------- */
+
+	if (ssol) {
+		unsigned ui;
+		GSList *l;
+
+		for (ui = 1, l = input_cells; l; ui++, l = l->next) {
+			GnmCell *cell = l->data;
+			char *name = g_strdup_printf ("X_%u", ui);
+			gnm_sub_solver_name_cell (ssol, cell, name);
+			g_free (name);
+		}
+	}
+
+	/* ---------------------------------------- */
+
+	progress = 2;
+	if (sp->options.assume_non_negative) progress++;
+	if (sp->options.assume_discrete) progress++;
+	progress += g_slist_length (sp->constraints);
+
+	go_io_count_progress_set (io_context, progress, 1);
+
+	/* ---------------------------------------- */
+
+	switch (sp->problem_type) {
+	case GNM_SOLVER_MINIMIZE:
+		g_string_append (objfunc, "Minimize\n");
+		break;
+	case GNM_SOLVER_MAXIMIZE:
+		g_string_append (objfunc, "Maximize\n");
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	go_io_count_progress_update (io_context, 1);
+
+	g_string_append (objfunc, " obj: ");
+	if (!glpk_affine_func (objfunc, target_cell, ssol,
+			       TRUE, 0, input_cells, err))
+		goto fail;
+	g_string_append (objfunc, "\n");
+	go_io_count_progress_update (io_context, 1);
+
+	/* ---------------------------------------- */
+
+	if (sp->options.assume_non_negative) {
+		GSList *l;
+		for (l = input_cells; l; l = l->next) {
+			GnmCell *cell = l->data;
+			g_string_append_printf (constraints, " %s >= 0\n",
+						glpk_var_name (ssol, cell));
+		}
+		go_io_count_progress_update (io_context, 1);
+	}
+
+	if (sp->options.assume_discrete) {
+		GSList *l;
+		for (l = input_cells; l; l = l->next) {
+			GnmCell *cell = l->data;
+			g_string_append_printf (integers, " %s\n",
+						glpk_var_name (ssol, cell));
+		}
+		go_io_count_progress_update (io_context, 1);
+	}
+
+ 	for (l = sp->constraints; l; l = l->next) {
+		GnmSolverConstraint *c = l->data;
+		const char *op = NULL;
+		gboolean right_small = TRUE;
+		int i;
+		gnm_float cl, cr;
+		GnmCell *lhs, *rhs;
+		GString *type = NULL;
+
+		switch (c->type) {
+		case GNM_SOLVER_LE:
+			op = "<=";
+			right_small = FALSE;
+			break;
+		case GNM_SOLVER_GE:
+			op = ">=";
+			break;
+		case GNM_SOLVER_EQ:
+			op = "=";
+			break;
+		case GNM_SOLVER_INTEGER:
+			type = integers;
+			break;
+		case GNM_SOLVER_BOOLEAN:
+			type = binaries;
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+
+		for (i = 0;
+		     gnm_solver_constraint_get_part (c, sp, i,
+						     &lhs, &cl,
+						     &rhs, &cr);
+		     i++) {
+			if (type) {
+				g_string_append_printf
+					(type, " %s\n",
+					 glpk_var_name (ssol, lhs));
+			} else {
+				gboolean ok;
+
+				g_string_append_c (constraints, ' ');
+
+				ok = glpk_affine_func
+					(constraints, lhs, ssol,
+					 FALSE, cl, input_cells, err);
+				if (!ok)
+					goto fail;
+
+				g_string_append_c (constraints, ' ');
+				g_string_append (constraints, op);
+				g_string_append_c (constraints, ' ');
+
+				ok = glpk_affine_func
+					(constraints, rhs, ssol,
+					 FALSE, cr, input_cells, err);
+				if (!ok)
+					goto fail;
+
+				g_string_append (constraints, "\n");
+			}
+		}
+
+		go_io_count_progress_update (io_context, 1);
+	}
+
+	/* ---------------------------------------- */
+
+	prg = g_string_new (NULL);
+	g_string_append_printf (prg,
+				"\\ Created by Gnumeric %s\n\n",
+				GNM_VERSION_FULL);
+	go_string_append_gstring (prg, objfunc);
+	g_string_append (prg, "\nSubject to\n");
+	go_string_append_gstring (prg, constraints);
+	if (integers->len > 0) {
+		g_string_append (prg, "\nGeneral\n");
+		go_string_append_gstring (prg, integers);
+	}
+	if (binaries->len > 0) {
+		g_string_append (prg, "\nBinary\n");
+		go_string_append_gstring (prg, binaries);
+	}
+	g_string_append (prg, "\nEnd\n");
+
+fail:
+	g_string_free (objfunc, TRUE);
+	g_string_free (constraints, TRUE);
+	g_string_free (integers, TRUE);
+	g_string_free (binaries, TRUE);
+	g_slist_free (input_cells);
+
+	return prg;
+}
+
+void
+glpk_file_save (GOFileSaver const *fs, GOIOContext *io_context,
+		WorkbookView const *wb_view, GsfOutput *output)
+{
+	Sheet *sheet = wb_view_cur_sheet (wb_view);
+	GError *err = NULL;
+	GString *prg;
+	GnmLocale *locale;
+	GnmSubSolver *ssol = g_object_get_data (G_OBJECT (fs), "solver");
+
+	go_io_progress_message (io_context,
+				_("Writing glpk file..."));
+
+	locale = gnm_push_C_locale ();
+	prg = glpk_create_program (sheet, io_context, ssol, &err);
+	gnm_pop_C_locale (locale);
+
+	workbook_recalc (sheet->workbook);
+
+	if (!prg) {
+		go_cmd_context_error_import (GO_CMD_CONTEXT (io_context),
+					     err ? err->message : "?");
+		goto fail;
+	}
+
+	gsf_output_write (output, prg->len, prg->str);
+	g_string_free (prg, TRUE);
+
+fail:
+	go_io_progress_unset (io_context);
+	if (err)
+		g_error_free (err);
+}
diff --git a/plugins/glpk/gnm-glpk.c b/plugins/glpk/gnm-glpk.c
new file mode 100644
index 0000000..1bd16f6
--- /dev/null
+++ b/plugins/glpk/gnm-glpk.c
@@ -0,0 +1,293 @@
+#include <gnumeric-config.h>
+#include "gnumeric.h"
+#include <tools/gnm-solver.h>
+#include "cell.h"
+#include "sheet.h"
+#include "value.h"
+#include "ranges.h"
+#include "gutils.h"
+#include <gsf/gsf-impl-utils.h>
+#include <gsf/gsf-input-textline.h>
+#include <gsf/gsf-input-stdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define PRIVATE_KEY "::glpk::"
+
+typedef struct {
+	GnmSubSolver *parent;
+	char *result_filename;
+	GnmSheetRange srinput;
+} GnmGlpk;
+
+static void
+gnm_glpk_cleanup (GnmGlpk *lp)
+{
+	gnm_sub_solver_clear (lp->parent);
+	if (lp->result_filename) {
+		g_unlink (lp->result_filename);
+		g_free (lp->result_filename);
+		lp->result_filename = NULL;
+	}
+}
+
+static void
+gnm_glpk_final (GnmGlpk *lp)
+{
+	gnm_glpk_cleanup (lp);
+	g_free (lp);
+}
+
+static gboolean
+write_program (GnmSolver *sol, WorkbookControl *wbc, GError **err)
+{
+	GnmSubSolver *subsol = GNM_SUB_SOLVER (sol);
+	GOFileSaver *fs;
+
+	fs = go_file_saver_for_mime_type ("application/glpk");
+	if (!fs) {
+		g_set_error (err, G_FILE_ERROR, 0,
+			     _("The GLPK exporter is not available."));
+		return FALSE;
+	}
+
+	return gnm_solver_saveas (sol, wbc, fs,
+				  "program-XXXXXX.cplex",
+				  &subsol->program_filename,
+				  err);
+}
+
+static void
+gnm_glpk_read_solution (GnmGlpk *lp)
+{
+	GnmSubSolver *subsol = lp->parent;
+	GnmSolver *sol = GNM_SOLVER (subsol);
+	GsfInput *input;
+	GsfInputTextline *tl;
+	const char *line;
+	unsigned rows, cols, c, r;
+	int pstat, dstat;
+	double val;
+	GnmSolverResult *result;
+	int width, height;
+
+	input = gsf_input_stdio_new (lp->result_filename, NULL);
+	if (!input)
+		return;
+
+	tl = GSF_INPUT_TEXTLINE (gsf_input_textline_new (input));
+	g_object_unref (input);
+
+	width = range_width (&lp->srinput.range);
+	height = range_height (&lp->srinput.range);
+	result = g_object_new (GNM_SOLVER_RESULT_TYPE, NULL);
+	result->solution = value_new_array_empty (width, height);
+
+	line = gsf_input_textline_utf8_gets (tl);
+	if (line == NULL ||
+	    sscanf (line, "%u %u", &rows, &cols) != 2 ||
+	    cols != g_hash_table_size (subsol->cell_from_name))
+		goto fail;
+
+	line = gsf_input_textline_utf8_gets (tl);
+	if (line == NULL ||
+	    sscanf (line, "%d %d %lg", &pstat, &dstat, &val) != 3)
+		goto fail;
+	result->value = val;
+	switch (pstat) {
+	case 2:
+	case 5:
+		result->quality = GNM_SOLVER_RESULT_OPTIMAL;
+		break;
+	case 3:
+	case 4:
+		result->quality = GNM_SOLVER_RESULT_INFEASIBLE;
+		break;
+	case 6:
+		result->quality = GNM_SOLVER_RESULT_UNBOUNDED;
+		break;
+	default:
+		goto fail;
+	}
+
+	for (r = 1; r <= rows; r++) {
+		line = gsf_input_textline_utf8_gets (tl);
+		if (!line)
+			goto fail;
+	}
+
+	for (c = 1; c <= cols; c++) {
+		double pval, dval;
+		unsigned cstat;
+		int x, y;
+
+		line = gsf_input_textline_utf8_gets (tl);
+		if (line == NULL ||
+		    sscanf (line, "%u %lg %lg", &cstat, &pval, &dval) != 3)
+			goto fail;
+
+		x = (c - 1) % width;
+		y = (c - 1) / width;
+		value_array_set (result->solution, x, y,
+				 value_new_float (pval));
+	}
+
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_DONE);
+	g_object_set (subsol, "result", result, NULL);
+	g_object_unref (result);
+
+	g_object_unref (tl);
+	return;
+
+fail:
+	g_object_unref (tl);
+	g_object_unref (result);
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_ERROR);
+}
+
+
+static void
+cb_child_exit (GPid pid, gint status, GnmGlpk *lp)
+{
+	GnmSubSolver *subsol = lp->parent;
+	GnmSolver *sol = GNM_SOLVER (subsol);
+
+	if (sol->status != GNM_SOLVER_STATUS_RUNNING)
+		return;
+
+	if (WIFEXITED (status)) {
+		switch (WEXITSTATUS (status)) {
+		case 0: {
+			GnmLocale *locale = gnm_push_C_locale ();
+			gnm_glpk_read_solution (lp);
+			gnm_pop_C_locale (locale);
+			break;
+		}
+		default:
+			break;
+		}
+	}
+
+	if (sol->status == GNM_SOLVER_STATUS_RUNNING)
+		gnm_solver_set_status (sol, GNM_SOLVER_STATUS_ERROR);
+}
+
+static void
+cb_child_setup (gpointer user)
+{
+	const char *lcvars[] = {
+		"LC_ALL",
+		"LC_MESSAGES",
+		"LC_CTYPE",
+		"LC_NUMERIC"
+	};
+	unsigned ui;
+
+	g_unsetenv ("LANG");
+	for (ui = 0; ui < G_N_ELEMENTS (lcvars); ui++) {
+		const char *v = lcvars[ui];
+		if (g_getenv (v))
+			g_setenv (v, "C", TRUE);
+	}
+}
+
+static gboolean
+gnm_glpk_prepare (GnmSolver *sol, WorkbookControl *wbc, GError **err,
+		  GnmGlpk *lp)
+{
+	gboolean ok;
+	int fd;
+
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_READY, FALSE);
+
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_PREPARING);
+	ok = write_program (sol, wbc, err);
+	if (!ok)
+		goto fail;
+
+	fd = g_file_open_tmp ("program-XXXXXX.out", &lp->result_filename, err);
+	if (fd == -1) {
+		g_set_error (err, G_FILE_ERROR, 0,
+			     _("Failed to create file for solution"));
+		goto fail;
+	}
+
+	close (fd);
+
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_PREPARED);
+
+	return TRUE;
+
+fail:
+	gnm_glpk_cleanup (lp);
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_ERROR);
+	return FALSE;
+}
+
+static gboolean
+gnm_glpk_start (GnmSolver *sol, WorkbookControl *wbc, GError **err,
+		GnmGlpk *lp)
+{
+	GnmSubSolver *subsol = GNM_SUB_SOLVER (sol);
+	gboolean ok;
+	gchar *argv[6];
+
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_PREPARED, FALSE);
+
+	argv[0] = (gchar *)"glpsol";
+	argv[1] = (gchar *)"--write";
+	argv[2] = lp->result_filename;
+	argv[3] = (gchar *)"--cpxlp";
+	argv[4] = subsol->program_filename;
+	argv[5] = NULL;
+
+	ok = gnm_sub_solver_spawn (subsol, argv,
+				   cb_child_setup, NULL,
+				   (GChildWatchFunc)cb_child_exit, lp,
+				   NULL, NULL,
+				   NULL, NULL,
+				   err);
+
+	return ok;
+}
+
+static gboolean
+gnm_glpk_stop (GnmSolver *sol, GError *err, GnmGlpk *lp)
+{
+	g_return_val_if_fail (sol->status != GNM_SOLVER_STATUS_RUNNING, FALSE);
+
+	gnm_glpk_cleanup (lp);
+
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_CANCELLED);
+
+	return TRUE;
+}
+
+GnmSolver *
+glpk_solver_factory (GnmSolverFactory *factory, GnmSolverParameters *params);
+
+GnmSolver *
+glpk_solver_factory (GnmSolverFactory *factory, GnmSolverParameters *params)
+{
+	GnmSolver *res = g_object_new (GNM_SUB_SOLVER_TYPE,
+				       "params", params,
+				       NULL);
+	GnmGlpk *lp = g_new0 (GnmGlpk, 1);
+
+	lp->parent = GNM_SUB_SOLVER (res);
+	gnm_sheet_range_from_value (&lp->srinput,
+				    gnm_solver_param_get_input (params));
+	if (lp->srinput.sheet) lp->srinput.sheet = params->sheet;
+
+	g_signal_connect (res, "prepare", G_CALLBACK (gnm_glpk_prepare), lp);
+	g_signal_connect (res, "start", G_CALLBACK (gnm_glpk_start), lp);
+	g_signal_connect (res, "stop", G_CALLBACK (gnm_glpk_stop), lp);
+
+	g_object_set_data_full (G_OBJECT (res), PRIVATE_KEY, lp,
+				(GDestroyNotify)gnm_glpk_final);
+
+	return res;
+}
diff --git a/plugins/glpk/plugin.xml.in b/plugins/glpk/plugin.xml.in
new file mode 100644
index 0000000..f9db0da
--- /dev/null
+++ b/plugins/glpk/plugin.xml.in
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin id="Gnumeric_glpk">
+	<information>
+		<_name>GLPK Linear Program Solver Interface</_name>
+		<_description>Solver Interface to GLPK</_description>
+	</information>
+	<loader type="Gnumeric_Builtin:module">
+		<attribute name="module_file" value="glpk"/>
+	</loader>
+	<services>
+		<service type="file_saver"
+			 id="glpk"
+			 save_scope="sheet"
+			 file_extension="cplex"
+			 mime_type="application/glpk"
+			 format_level="write_only">
+			<information>
+				<_description>GLPK Linear Program Solver</_description>
+			</information>
+		</service>
+		<service type="solver"
+			 id="glpk"
+			 problem_type="mip">
+			<information>
+			  <_description>GLPK</_description>
+			</information>
+		</service>
+	</services>
+</plugin>
diff --git a/plugins/lpsolve/gnm-lpsolve.c b/plugins/lpsolve/gnm-lpsolve.c
index 85b56b9..27605a4 100644
--- a/plugins/lpsolve/gnm-lpsolve.c
+++ b/plugins/lpsolve/gnm-lpsolve.c
@@ -30,6 +30,13 @@ gnm_lpsolve_cleanup (GnmLPSolve *lp)
 	}
 }
 
+static void
+gnm_lpsolve_final (GnmLPSolve *lp)
+{
+	gnm_lpsolve_cleanup (lp);
+	g_free (lp);
+}
+
 static gboolean
 write_program (GnmSolver *sol, WorkbookControl *wbc, GError **err)
 {
@@ -184,6 +191,9 @@ cb_child_exit (GPid pid, gint status, GnmLPSolve *lp)
 			new_status = GNM_SOLVER_STATUS_ERROR;
 			break;
 		}
+	} else {
+		/* Something bad.  */
+		new_status = GNM_SOLVER_STATUS_ERROR;
 	}
 
 	gnm_solver_set_status (sol, new_status);
@@ -286,7 +296,7 @@ lpsolve_solver_factory (GnmSolverFactory *factory, GnmSolverParameters *params)
 	g_signal_connect (res, "stop", G_CALLBACK (gnm_lpsolve_stop), lp);
 
 	g_object_set_data_full (G_OBJECT (res), PRIVATE_KEY, lp,
-				(GDestroyNotify)g_free);
+				(GDestroyNotify)gnm_lpsolve_final);
 
 	return res;
 }
diff --git a/plugins/lpsolve/lpsolve-write.c b/plugins/lpsolve/lpsolve-write.c
index 29cf6f0..8f19e1f 100644
--- a/plugins/lpsolve/lpsolve-write.c
+++ b/plugins/lpsolve/lpsolve-write.c
@@ -157,7 +157,8 @@ fail:
 }
 
 static GString *
-lpsolve_create_program (Sheet *sheet, GOIOContext *io_context, GError **err)
+lpsolve_create_program (Sheet *sheet, GOIOContext *io_context,
+			GnmSubSolver *ssol, GError **err)
 {
 	GnmSolverParameters *sp = sheet->solver_parameters;
 	GString *prg = NULL;
@@ -319,12 +320,13 @@ lpsolve_file_save (GOFileSaver const *fs, GOIOContext *io_context,
 	GError *err = NULL;
 	GString *prg;
 	GnmLocale *locale;
+	GnmSubSolver *ssol = g_object_get_data (G_OBJECT (fs), "solver");
 
 	go_io_progress_message (io_context,
 				_("Writing lpsolve file..."));
 
 	locale = gnm_push_C_locale ();
-	prg = lpsolve_create_program (sheet, io_context, &err);
+	prg = lpsolve_create_program (sheet, io_context, ssol, &err);
 	gnm_pop_C_locale (locale);
 
 	workbook_recalc (sheet->workbook);
diff --git a/plugins/lpsolve/plugin.xml.in b/plugins/lpsolve/plugin.xml.in
index 51bd7ee..deedb12 100644
--- a/plugins/lpsolve/plugin.xml.in
+++ b/plugins/lpsolve/plugin.xml.in
@@ -2,7 +2,7 @@
 <plugin id="Gnumeric_lpsolve">
 	<information>
 		<_name>LPSolve Linear Program Solver Interface</_name>
-		<_description>Specialized Export of Solver Data to LPSolve</_description>
+		<_description>Solver Interface to LPSolve</_description>
 	</information>
 	<loader type="Gnumeric_Builtin:module">
 		<attribute name="module_file" value="lpsolve"/>
diff --git a/po-functions/POTFILES.in b/po-functions/POTFILES.in
index 9dd8303..b1daa3c 100644
--- a/po-functions/POTFILES.in
+++ b/po-functions/POTFILES.in
@@ -31,11 +31,14 @@ plugins/fn-stat/functions.c
 plugins/fn-string/functions.c
 plugins/fn-tsa/functions.c
 plugins/gda/plugin-gda.c
+plugins/glpk/glpk-write.c
+plugins/glpk/gnm-glpk.c
 plugins/gnome-db/plugin-gnomedb.c
 plugins/html/html_read.c
 plugins/html/roff.c
 plugins/lotus-123/boot.c
 plugins/lotus-123/lotus.c
+plugins/lpsolve/gnm-lpsolve.c
 plugins/lpsolve/lpsolve-write.c
 plugins/mps/mps.c
 plugins/mps/parser.c
@@ -197,6 +200,7 @@ src/tools/dao.c
 src/tools/data-shuffling.c
 src/tools/fill-series.c
 src/tools/filter.c
+src/tools/gnm-solver.c
 src/tools/random-generator.c
 src/tools/random-generator-cor.c
 src/tools/scenarios.c
diff --git a/po-functions/POTFILES.skip b/po-functions/POTFILES.skip
index c5d0ff2..000ac16 100644
--- a/po-functions/POTFILES.skip
+++ b/po-functions/POTFILES.skip
@@ -37,6 +37,7 @@ plugins/fn-string/plugin.xml.in
 plugins/fn-tsa/plugin.xml.in
 plugins/gda/plugin.xml.in
 plugins/gda/ui.xml.in
+plugins/glpk/plugin.xml.in
 plugins/gnome-db/plugin.xml.in
 plugins/gnome-glossary/plugin.xml.in
 plugins/html/plugin.xml.in
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a02eb50..7eba53c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -60,6 +60,9 @@ plugins/fn-tsa/plugin.xml.in
 plugins/gda/plugin-gda.c
 plugins/gda/plugin.xml.in
 plugins/gda/ui.xml.in
+plugins/glpk/glpk-write.c
+plugins/glpk/gnm-glpk.c
+plugins/glpk/plugin.xml.in
 plugins/gnome-db/plugin-gnomedb.c
 plugins/gnome-db/plugin.xml.in
 plugins/gnome-glossary/plugin.xml.in
@@ -69,6 +72,7 @@ plugins/html/roff.c
 plugins/lotus-123/boot.c
 plugins/lotus-123/lotus.c
 plugins/lotus-123/plugin.xml.in
+plugins/lpsolve/gnm-lpsolve.c
 plugins/lpsolve/lpsolve-write.c
 plugins/lpsolve/plugin.xml.in
 plugins/mps/mps.c
@@ -326,6 +330,7 @@ src/tools/dao.c
 src/tools/data-shuffling.c
 src/tools/fill-series.c
 src/tools/filter.c
+src/tools/gnm-solver.c
 src/tools/random-generator.c
 src/tools/random-generator-cor.c
 src/tools/scenarios.c
diff --git a/src/dialogs/dialog-solver.c b/src/dialogs/dialog-solver.c
index 644ab48..42c7990 100644
--- a/src/dialogs/dialog-solver.c
+++ b/src/dialogs/dialog-solver.c
@@ -590,6 +590,8 @@ run_solver (SolverState *state, GnmSolverParameters *param)
 			       &err);
 	if (ok) {
 		dialog_res = go_gtk_dialog_run (dialog, top);
+		if (dialog_res == GTK_RESPONSE_YES && !sol->result)
+			dialog_res = GTK_RESPONSE_DELETE_EVENT;
 	} else {
 		gtk_widget_destroy (GTK_WIDGET (dialog));
 		go_gtk_notice_dialog (top, GTK_MESSAGE_ERROR,
diff --git a/src/tools/gnm-solver.c b/src/tools/gnm-solver.c
index 96deb89..131c018 100644
--- a/src/tools/gnm-solver.c
+++ b/src/tools/gnm-solver.c
@@ -21,6 +21,17 @@
 
 /* ------------------------------------------------------------------------- */
 
+static gboolean
+debug_solver (void)
+{
+	static int debug = -1;
+	if (debug == -1)
+		debug = gnm_debug_flag ("solver");
+	return debug;
+}
+
+/* ------------------------------------------------------------------------- */
+
 GType
 gnm_solver_status_get_type (void)
 {
@@ -896,6 +907,11 @@ gnm_solver_saveas (GnmSolver *solver, WorkbookControl *wbc,
 		return FALSE;
 	}
 
+	/* Give the saver a way to talk to the solver.  */
+	g_object_set_data_full (G_OBJECT (fs),
+				"solver", g_object_ref (solver),
+				(GDestroyNotify)g_object_unref);
+
 	output = gsf_output_stdio_new_FILE (*filename, file, TRUE);
 	io_context = go_io_context_new (GO_CMD_CONTEXT (wbc));
 	wbv_save_to_output (wbv, fs, output, io_context);
@@ -903,6 +919,8 @@ gnm_solver_saveas (GnmSolver *solver, WorkbookControl *wbc,
 	g_object_unref (io_context);
 	g_object_unref (output);
 
+	g_object_set_data (G_OBJECT (fs), "solver", NULL);
+
 	if (!ok) {
 		g_set_error (err, G_FILE_ERROR, 0,
 			     _("Failed to save linear program"));
@@ -1046,6 +1064,9 @@ gnm_sub_solver_clear (GnmSubSolver *subsol)
 		g_free (subsol->program_filename);
 		subsol->program_filename = NULL;
 	}
+
+	g_hash_table_remove_all (subsol->cell_from_name);
+	g_hash_table_remove_all (subsol->name_from_cell);
 }
 
 static void
@@ -1065,11 +1086,20 @@ gnm_sub_solver_init (GnmSubSolver *subsol)
 
 	for (i = 0; i <= 2; i++)
 		subsol->fd[i] = -1;
+
+	subsol->cell_from_name =
+		g_hash_table_new_full (g_str_hash, g_str_equal,
+				       g_free, NULL);
+	subsol->name_from_cell =
+		g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static void
 cb_child_exit (GPid pid, gint status, GnmSubSolver *subsol)
 {
+	if (debug_solver ())
+		g_printerr ("Solver process exited.\n");
+
 	subsol->child_watch = 0;
 	if (subsol->child_exit)
 		subsol->child_exit (pid, status, subsol->exit_data);
@@ -1095,6 +1125,12 @@ gnm_sub_solver_spawn (GnmSubSolver *subsol,
 	if (!g_path_is_absolute (argv[0]))
 		spflags |= G_SPAWN_SEARCH_PATH;
 
+	if (io_stdout == NULL)
+		spflags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+
+	if (debug_solver ())
+		g_printerr ("Spawning %s\n", argv[0]);
+
 	ok = g_spawn_async_with_pipes
 		(g_get_home_dir (),  /* PWD */
 		 argv,
@@ -1103,8 +1139,8 @@ gnm_sub_solver_spawn (GnmSubSolver *subsol,
 		 child_setup, setup_data,
 		 &subsol->child_pid,
 		 NULL,			/* stdin */
-		 &subsol->fd[1],	/* stdout */
-		 &subsol->fd[2],	/* stderr */
+		 io_stdout ? &subsol->fd[1] : NULL,	/* stdout */
+		 io_stdout ? &subsol->fd[2] : NULL,	/* stderr */
 		 err);
 	if (!ok)
 		goto fail;
@@ -1151,6 +1187,35 @@ fail:
 	return FALSE;
 }
 
+const char *
+gnm_sub_solver_name_cell (GnmSubSolver *subsol, GnmCell const *cell,
+			  const char *name)
+{
+	char *name_copy = g_strdup (name);
+
+	g_hash_table_insert (subsol->cell_from_name,
+			     name_copy,
+			     (gpointer)cell);
+	g_hash_table_insert (subsol->name_from_cell,
+			     (gpointer)cell,
+			     name_copy);
+
+	return name_copy;
+}
+
+GnmCell *
+gnm_sub_solver_find_cell (GnmSubSolver *subsol, const char *name)
+{
+	return g_hash_table_lookup (subsol->cell_from_name, name);
+}
+
+const char *
+gnm_sub_solver_get_cell_name (GnmSubSolver *subsol,
+			      GnmCell const *cell)
+{
+	return g_hash_table_lookup (subsol->name_from_cell, (gpointer)cell);
+}
+
 void
 gnm_sub_solver_flush (GnmSubSolver *subsol)
 {
@@ -1244,7 +1309,7 @@ gnm_solver_factory_create (GnmSolverFactory *factory,
 void
 gnm_solver_db_register (GnmSolverFactory *factory)
 {
-	if (gnm_debug_flag ("solver"))
+	if (debug_solver ())
 		g_printerr ("Registering %s\n", factory->id);
 	g_object_ref (factory);
 	solvers = g_slist_prepend (solvers, factory);
@@ -1253,7 +1318,7 @@ gnm_solver_db_register (GnmSolverFactory *factory)
 void
 gnm_solver_db_unregister (GnmSolverFactory *factory)
 {
-	if (gnm_debug_flag ("solver"))
+	if (debug_solver ())
 		g_printerr ("Unregistering %s\n", factory->id);
 	solvers = g_slist_remove (solvers, factory);
 	g_object_unref (factory);
diff --git a/src/tools/gnm-solver.h b/src/tools/gnm-solver.h
index 37de650..d93da60 100644
--- a/src/tools/gnm-solver.h
+++ b/src/tools/gnm-solver.h
@@ -245,6 +245,10 @@ typedef struct {
 
 	char *program_filename;
 
+	/* Hashes between char* and cell*.  */
+	GHashTable *cell_from_name;
+	GHashTable *name_from_cell;
+
 	GPid child_pid;
 	guint child_watch;
 
@@ -277,6 +281,13 @@ gboolean gnm_sub_solver_spawn
 
 void gnm_sub_solver_flush (GnmSubSolver *subsol);
 
+const char *gnm_sub_solver_name_cell (GnmSubSolver *subsol,
+				      GnmCell const *cell,
+				      const char *name);
+GnmCell *gnm_sub_solver_find_cell (GnmSubSolver *subsol, const char *name);
+const char *gnm_sub_solver_get_cell_name (GnmSubSolver *subsol,
+					  GnmCell const *cell);
+
 /* ------------------------------------------------------------------------- */
 
 #define GNM_SOLVER_FACTORY_TYPE        (gnm_solver_factory_get_type ())



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