[gnumeric] solver: use external solver.



commit 6559e32c38d4bed0cba2f82760e001419c1a074e
Author: Morten Welinder <terra gnome org>
Date:   Wed Nov 11 16:44:42 2009 -0500

    solver: use external solver.

 NEWS                          |    1 +
 plugins/lpsolve/Makefile.am   |    5 +-
 plugins/lpsolve/gnm-lpsolve.c |  292 ++++++++++++++++++
 plugins/lpsolve/plugin.xml.in |    7 +
 src/dialogs/dialog-solver.c   |  310 +++++++++++++++++++-
 src/gnm-marshalers.list       |    1 +
 src/gnm-plugin.c              |  195 ++++++++++++-
 src/gnm-plugin.h              |   11 +
 src/libgnumeric.c             |    2 +
 src/tools/Makefile.am         |    2 +
 src/tools/gnm-solver.c        |  665 +++++++++++++++++++++++++++++++++++++++++
 src/tools/gnm-solver.h        |  184 ++++++++++++
 12 files changed, 1661 insertions(+), 14 deletions(-)
---
diff --git a/NEWS b/NEWS
index 1bb55cc..d3c31a6 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,7 @@ Morten:
 	* More solver fixes.
 	* Plug another mps leak.
 	* Fix problem with remote uris.  [#601274]
+	* Use external solver program.
 
 --------------------------------------------------------------------------
 Gnumeric 1.9.15
diff --git a/plugins/lpsolve/Makefile.am b/plugins/lpsolve/Makefile.am
index d07528b..dbddb99 100644
--- a/plugins/lpsolve/Makefile.am
+++ b/plugins/lpsolve/Makefile.am
@@ -8,8 +8,9 @@ xmldir = $(gnumeric_plugin_lpsolvedir)
 gnumeric_plugin_lpsolve_LTLIBRARIES = lpsolve.la
 lpsolve_la_LDFLAGS = -module $(GNUMERIC_PLUGIN_LDFLAGS)
 lpsolve_la_SOURCES = \
-    boot.h boot.c \
-    lpsolve-write.c
+	boot.h boot.c \
+	gnm-lpsolve.c \
+	lpsolve-write.c
 
 xml_in_files = plugin.xml.in
 xml_DATA = $(xml_in_files:.xml.in=.xml)
diff --git a/plugins/lpsolve/gnm-lpsolve.c b/plugins/lpsolve/gnm-lpsolve.c
new file mode 100644
index 0000000..ea8f956
--- /dev/null
+++ b/plugins/lpsolve/gnm-lpsolve.c
@@ -0,0 +1,292 @@
+#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 <gsf/gsf-impl-utils.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#define PRIVATE_KEY "::lpsolve::"
+
+typedef struct {
+	GnmSubSolver *parent;
+	GnmSolverResult *result;
+	GnmSheetRange srinput;
+	enum { SEC_UNKNOWN, SEC_VALUES } section;
+} GnmLPSolve;
+
+static void
+gnm_lpsolve_cleanup (GnmLPSolve *lp)
+{
+	gnm_sub_solver_clear (lp->parent);
+
+	if (lp->result) {
+		g_object_unref (lp->result);
+		lp->result = NULL;
+	}
+}
+
+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/lpsolve");
+	if (!fs) {
+		g_set_error (err, G_FILE_ERROR, 0,
+			     _("The LPSolve exporter is not available."));
+		return FALSE;
+	}
+
+	return gnm_solver_saveas (sol, wbc, fs, 
+				  "program-XXXXXX.lp",
+				  &subsol->program_filename,
+				  err);
+}
+
+static GnmSolverResult *
+gnm_lpsolve_start_solution (GnmLPSolve *lp)
+{
+	g_return_val_if_fail (lp->result == NULL, NULL);
+
+	lp->result = g_object_new (GNM_SOLVER_RESULT_TYPE, NULL);
+	lp->result->solution = value_new_array_empty
+		(range_width (&lp->srinput.range),
+		 range_height (&lp->srinput.range));
+
+	return lp->result;
+}
+
+static void
+gnm_lpsolve_flush_solution (GnmLPSolve *lp)
+{
+	if (lp->result) {
+		g_object_set (lp->parent, "result", lp->result, NULL);
+		g_object_unref (lp->result);
+		lp->result = NULL;
+	}
+}
+
+
+static gboolean
+cb_read_stdout (GIOChannel *channel, GIOCondition cond, GnmLPSolve *lp)
+{
+	GnmSolver *sol = GNM_SOLVER (lp->parent);
+	const char obj_line_prefix[] = "Value of objective function:";
+	size_t obj_line_len = sizeof (obj_line_prefix) - 1;
+	const char val_header_line[] = "Actual values of the variables:";
+	size_t val_header_len = sizeof (val_header_line) - 1;
+
+	do {
+		GIOStatus status;
+		gchar *line = NULL;
+		gsize tpos;
+
+		status = g_io_channel_read_line (channel,
+						 &line, NULL, &tpos,
+						 NULL);
+		if (status != G_IO_STATUS_NORMAL)
+			break;
+
+		line[tpos] = 0;
+
+		if (line[0] == 0 || g_ascii_isspace (line[0]))
+			lp->section = SEC_UNKNOWN;
+		else if (lp->section == SEC_UNKNOWN &&
+			 !strncmp (line, obj_line_prefix, obj_line_len)) {
+			GnmSolverResult *r;
+			gnm_lpsolve_flush_solution (lp);
+			r = gnm_lpsolve_start_solution (lp);
+			r->quality = GNM_SOLVER_RESULT_FEASIBLE;
+			r->value = g_ascii_strtod (line + obj_line_len, NULL);
+		} else if (lp->section == SEC_UNKNOWN &&
+			   !strncmp (line, val_header_line, val_header_len)) {
+			lp->section = SEC_VALUES;
+		} else if (lp->section == SEC_VALUES && lp->result) {
+			GnmSolverResult *r = lp->result;
+			Sheet *sheet = sol->params->sheet;
+			GnmCellPos pos;
+			int x, y;
+			double v;
+			const char *after =
+				cellpos_parse (line, &sheet->size, &pos, FALSE);
+			if (!after || *after != ' ') {
+				lp->section = SEC_UNKNOWN;
+				continue;
+			}
+			v = g_ascii_strtod (after, NULL);
+			x = pos.col - lp->srinput.range.start.col;
+			y = pos.row - lp->srinput.range.start.row;
+			if (x >= 0 &&
+			    x < value_area_get_width (r->solution, NULL) &&
+			    y >= 0 &&
+			    y < value_area_get_height (r->solution, NULL))
+				value_array_set (r->solution, x, y,
+						 value_new_float (v));
+		}
+		g_free (line);
+	} while (1);
+
+	return TRUE;
+}
+
+
+static void
+cb_child_exit (GPid pid, gint status, GnmLPSolve *lp)
+{
+	GnmSubSolver *subsol = lp->parent;
+	GnmSolver *sol = GNM_SOLVER (subsol);
+	GnmSolverStatus new_status = GNM_SOLVER_STATUS_DONE;
+
+	if (sol->status != GNM_SOLVER_STATUS_RUNNING)
+		return;
+
+	if (WIFEXITED (status)) {
+		GnmSolverResult *r;
+
+		switch (WEXITSTATUS (status)) {
+		case 0: /* Optimal */
+			gnm_sub_solver_flush (subsol);
+			if (lp->result)
+				lp->result->quality = GNM_SOLVER_RESULT_OPTIMAL;
+			gnm_lpsolve_flush_solution (lp);
+			break;
+
+		case 2: /* Infeasible */
+			r = gnm_lpsolve_start_solution (lp);
+			r->quality = GNM_SOLVER_RESULT_INFEASIBLE;
+			gnm_lpsolve_flush_solution (lp);
+			break;
+
+		case 3: /* Unbounded */
+			r = gnm_lpsolve_start_solution (lp);
+			r->quality = GNM_SOLVER_RESULT_UNBOUNDED;
+			gnm_lpsolve_flush_solution (lp);
+			break;
+
+		case 1: /* Suboptimal */
+		case 4: /* Degenerate */
+			gnm_sub_solver_flush (subsol);
+			gnm_lpsolve_flush_solution (lp);
+			break;
+
+		default:
+		case 5: /* Numfailure */
+		case 6: /* Userabort */
+		case 7: /* Timeout */
+		case 8: /* Running (eh?) */
+		case 9: /* Presolved (eh?) */
+			new_status = GNM_SOLVER_STATUS_ERROR;
+			break;
+		}
+	}
+
+	gnm_solver_set_status (sol, new_status);
+}
+
+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_lpsolve_prepare (GnmSolver *sol, WorkbookControl *wbc, GError **err,
+		     GnmLPSolve *lp)
+{
+	gboolean ok;
+
+	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)
+		gnm_solver_set_status (sol, GNM_SOLVER_STATUS_PREPARED);
+	else {
+		gnm_lpsolve_cleanup (lp);
+		gnm_solver_set_status (sol, GNM_SOLVER_STATUS_ERROR);
+	}
+
+	return ok;
+}
+
+static gboolean
+gnm_lpsolve_start (GnmSolver *sol, WorkbookControl *wbc, GError **err,
+		   GnmLPSolve *lp)
+{
+	GnmSubSolver *subsol = GNM_SUB_SOLVER (sol);
+	gboolean ok;
+	gchar *argv[4];
+
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_PREPARED, FALSE);
+
+	argv[0] = (gchar *)"lp_solve";
+	argv[1] = (gchar *)"-i";
+	argv[2] = subsol->program_filename;
+	argv[3] = NULL;
+
+	ok = gnm_sub_solver_spawn (subsol, argv,
+				   cb_child_setup, NULL,
+				   (GChildWatchFunc)cb_child_exit, lp,
+				   (GIOFunc)cb_read_stdout, lp,
+				   NULL, NULL,
+				   err);
+
+	return ok;
+}
+
+static gboolean
+gnm_lpsolve_stop (GnmSolver *sol, GError *err, GnmLPSolve *lp)
+{
+	g_return_val_if_fail (sol->status != GNM_SOLVER_STATUS_RUNNING, FALSE);
+
+	gnm_lpsolve_cleanup (lp);
+
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_CANCELLED);
+
+	return TRUE;
+}
+
+GnmSolver *
+lpsolve_solver_factory (GnmSolverFactory *factory, SolverParameters *params);
+
+GnmSolver *
+lpsolve_solver_factory (GnmSolverFactory *factory, SolverParameters *params)
+{
+	GnmSolver *res = g_object_new (GNM_SUB_SOLVER_TYPE,
+					  "params", params,
+					  NULL);
+	GnmLPSolve *lp = g_new0 (GnmLPSolve, 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_lpsolve_prepare), lp);
+	g_signal_connect (res, "start", G_CALLBACK (gnm_lpsolve_start), lp);
+	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);
+
+	return res;
+}
diff --git a/plugins/lpsolve/plugin.xml.in b/plugins/lpsolve/plugin.xml.in
index 8d3dfa7..51bd7ee 100644
--- a/plugins/lpsolve/plugin.xml.in
+++ b/plugins/lpsolve/plugin.xml.in
@@ -18,5 +18,12 @@
 				<_description>LPSolve Linear Program Solver</_description>
 			</information>
 		</service>
+		<service type="solver"
+			 id="lpsolve"
+			 problem_type="mip">
+			<information>
+			  <_description>LPSolve</_description>
+			</information>
+		</service>
 	</services>
 </plugin>
diff --git a/src/dialogs/dialog-solver.c b/src/dialogs/dialog-solver.c
index 4a179b2..a1ed74a 100644
--- a/src/dialogs/dialog-solver.c
+++ b/src/dialogs/dialog-solver.c
@@ -42,6 +42,8 @@
 #include <parse-util.h>
 #include <ranges.h>
 #include <commands.h>
+#include <clipboard.h>
+#include <tools/gnm-solver.h>
 #include <widgets/gnumeric-expr-entry.h>
 
 #include <glade/glade.h>
@@ -76,6 +78,18 @@ typedef struct {
 	SolverConstraint    *constr;
 	GtkWidget           *warning_dialog;
 
+	struct {
+		GnmSolver   *solver;
+		GtkDialog   *dialog;
+		GtkWidget   *timer_widget;
+		guint       timer_source;
+		time_t      timer_start;
+		GtkWidget   *status_widget;
+		GtkWidget   *result_widget;
+		GtkWidget   *stop_button;
+		GtkWidget   *ok_button;
+	} run;
+
 	Sheet		    *sheet;
 	WBCGtk              *wbcg;
 } SolverState;
@@ -465,6 +479,275 @@ solver_reporting (SolverState *state, SolverResults *res)
 }
 
 static void
+cb_stop_solver (SolverState *state)
+{
+	GnmSolver *sol = state->run.solver;
+
+	switch (sol->status) {
+	case GNM_SOLVER_STATUS_RUNNING: {
+		gboolean ok = gnm_solver_stop (sol, NULL);
+		if (!ok) {
+			g_warning ("Failed to stop solver!");
+		}
+		break;
+	}
+
+	default:
+		break;
+	}
+}
+
+static void
+cb_notify_status (SolverState *state)
+{
+	GnmSolver *sol = state->run.solver;
+	const char *text;
+	gboolean finished = gnm_solver_finished (sol);
+
+	switch (sol->status) {
+	case GNM_SOLVER_STATUS_READY:
+		text = _("Ready");
+		break;
+	case GNM_SOLVER_STATUS_PREPARING:
+		text = _("Preparing");
+		break;
+	case GNM_SOLVER_STATUS_PREPARED:
+		text = _("Prepared");
+		break;
+	case GNM_SOLVER_STATUS_RUNNING:
+		text = _("Running");
+		break;
+	case GNM_SOLVER_STATUS_DONE:
+		text = _("Done");
+		break;
+	default:
+	case GNM_SOLVER_STATUS_ERROR:
+		text = _("Error");
+		break;
+	case GNM_SOLVER_STATUS_CANCELLED:
+		text = _("Cancelled");
+		break;
+	}
+
+	gtk_label_set_text (GTK_LABEL (state->run.status_widget), text);
+
+	if (finished) {
+		if (state->run.timer_source) {
+			g_source_remove (state->run.timer_source);
+			state->run.timer_source = 0;
+		}
+	}
+
+	gtk_widget_set_sensitive (state->run.stop_button, !finished);
+	gtk_widget_set_sensitive (state->run.ok_button, finished);
+}
+
+static void
+cb_notify_result (SolverState *state)
+{
+	GnmSolver *sol = state->run.solver;
+	GnmSolverResult *r = sol->result;
+	char *txt;
+
+	switch (r ? r->quality : GNM_SOLVER_RESULT_NONE) {
+	default:
+	case GNM_SOLVER_RESULT_NONE:
+		txt = g_strdup ("");
+		break;
+
+	case GNM_SOLVER_RESULT_FEASIBLE:
+		txt = g_strdup_printf ("Feasible: %.15g", (double)r->value);
+		break;
+
+	case GNM_SOLVER_RESULT_OPTIMAL:
+		txt = g_strdup_printf ("Optimal: %.15g", (double)r->value);
+		break;
+
+	case GNM_SOLVER_RESULT_INFEASIBLE:
+		txt = g_strdup ("Infeasible");
+		break;
+
+	case GNM_SOLVER_RESULT_UNBOUNDED:
+		txt = g_strdup ("Unbounded");
+		break;
+	}
+
+	gtk_label_set_text (GTK_LABEL (state->run.result_widget), txt);
+	g_free (txt);
+}
+
+
+static gboolean
+cb_timer_tick (SolverState *state)
+{
+	int secs = time (NULL) - state->run.timer_start;
+	int hh = secs / 3600;
+	int mm = secs / 60 % 60;
+	int ss = secs % 60;
+	char *txt = hh
+		? g_strdup_printf ("%02d:%02d:%02d", hh, mm, ss)
+		: g_strdup_printf ("%02d:%02d", mm, ss);
+
+	gtk_label_set_text (GTK_LABEL (state->run.timer_widget), txt);
+	g_free (txt);
+
+	return TRUE;
+}
+
+static GnmSolverResult *
+run_solver (SolverState *state, SolverParameters *param)
+{
+	GtkDialog *dialog;
+	GtkWidget *hbox;
+	int dialog_res;
+	GError *err = NULL;
+	gboolean ok;
+	GnmSheetRange sr;
+	GOUndo *undo = NULL;
+	GnmSolver *sol = NULL;
+	GnmValue const *vinput;
+	GtkWindow *top = GTK_WINDOW (gtk_widget_get_toplevel (state->dialog));
+	GSList *solvers;
+	GnmSolverResult *res = NULL;
+
+	for (solvers = gnm_solver_db_get (); solvers; solvers = solvers->next) {
+		GnmSolverFactory *entry = solvers->data;
+		if (param->problem_type != entry->type)
+			continue;
+		g_printerr ("Trying solver %s...\n", entry->id);
+		sol = gnm_solver_factory_create (entry, param);
+		if (sol)
+			break;
+	}
+	if (!sol) {
+		go_gtk_notice_dialog (top, GTK_MESSAGE_ERROR,
+				      _("No suitable solver available."));
+		goto fail;
+	}
+
+	state->run.solver = sol;
+
+	vinput = gnm_solver_param_get_input (param);
+	gnm_sheet_range_from_value (&sr, vinput);
+	if (!sr.sheet) sr.sheet = param->sheet;
+	undo = clipboard_copy_range_undo (sr.sheet, &sr.range);
+
+	dialog = (GtkDialog *)gtk_dialog_new_with_buttons
+		(_("Running Solver"),
+		 wbcg_toplevel (state->wbcg), 0,
+		 NULL);
+	state->run.stop_button =
+		go_gtk_dialog_add_button (dialog,
+					  _("Stop"),
+					  GTK_STOCK_STOP,
+					  GTK_RESPONSE_NO);
+	go_widget_set_tooltip_text
+		(state->run.stop_button,
+		 _("Stop the running solver"));
+	g_signal_connect_swapped (G_OBJECT (state->run.stop_button),
+				  "clicked", G_CALLBACK (cb_stop_solver),
+				  state);
+
+	state->run.ok_button =
+		go_gtk_dialog_add_button (dialog,
+					  _("OK"),
+					  GTK_STOCK_OK,
+					  GTK_RESPONSE_YES);
+
+	hbox = gtk_hbox_new (FALSE, 2);
+
+	state->run.timer_widget = gtk_label_new ("");
+	gtk_box_pack_start (GTK_BOX (hbox), state->run.timer_widget,
+			    TRUE, TRUE, 0);
+
+	state->run.status_widget = gtk_label_new ("");
+	gtk_box_pack_start (GTK_BOX (hbox), state->run.status_widget,
+			    TRUE, TRUE, 0);
+
+	state->run.result_widget = gtk_label_new ("");
+	gtk_box_pack_start (GTK_BOX (hbox), state->run.result_widget,
+			    TRUE, TRUE, 0);
+
+	gtk_box_pack_start (GTK_BOX (dialog->vbox), hbox, TRUE, TRUE, 0);
+	gtk_widget_show_all (GTK_WIDGET (dialog));
+
+	g_signal_connect_swapped (G_OBJECT (sol),
+				  "notify::status",
+				  G_CALLBACK (cb_notify_status),
+				  state);
+	cb_notify_status (state);
+
+	g_signal_connect_swapped (G_OBJECT (sol),
+				  "notify::result",
+				  G_CALLBACK (cb_notify_result),
+				  state);
+	cb_notify_result (state);
+
+	state->run.dialog = g_object_ref (dialog);
+	g_object_ref (state->run.timer_widget);
+	g_object_ref (state->run.status_widget);
+	state->run.timer_source = g_timeout_add_seconds
+		(1, (GSourceFunc)cb_timer_tick, state);
+	state->run.timer_start = time (NULL);
+	cb_timer_tick (state);
+
+	/* ---------------------------------------- */
+
+	ok = gnm_solver_start (sol,
+			       WORKBOOK_CONTROL (state->wbcg),
+			       &err);
+	if (ok) {
+		dialog_res = go_gtk_dialog_run (dialog, top);
+	} else {
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		go_gtk_notice_dialog (top, GTK_MESSAGE_ERROR,
+				      "%s", err->message);
+		dialog_res = GTK_RESPONSE_DELETE_EVENT;
+	}
+
+	/* ---------------------------------------- */
+
+	gtk_widget_destroy (GTK_WIDGET (state->run.dialog));
+	if (state->run.timer_source) {
+		g_source_remove (state->run.timer_source);
+		state->run.timer_source = 0;
+	}
+	g_object_unref (state->run.status_widget);
+	g_object_unref (state->run.timer_widget);
+	g_object_unref (state->run.dialog);
+
+	switch (dialog_res) {
+	default:
+	case GTK_RESPONSE_NO:
+	case GTK_RESPONSE_DELETE_EVENT:
+		break;
+
+	case GTK_RESPONSE_YES: {
+		GOUndo *redo;
+
+		gnm_solver_store_result (sol);
+		redo = clipboard_copy_range_undo (sr.sheet, &sr.range);
+		cmd_solver (WORKBOOK_CONTROL (state->wbcg), undo, redo);
+		res = g_object_ref (sol->result);
+		undo = redo = NULL;
+		break;
+	}
+	}
+
+fail:
+	if (undo)
+		g_object_unref (undo);
+
+	if (state->run.solver) {
+		g_object_unref (state->run.solver);
+		state->run.solver = NULL;
+	}
+
+	return res;
+}
+
+
+static void
 solver_add_scenario (SolverState *state, SolverResults *res, gchar const *name)
 {
 	SolverParameters *param = res->param;
@@ -493,7 +776,7 @@ static void
 cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 			 SolverState *state)
 {
-	SolverResults           *res;
+	GnmSolverResult           *res;
 	GnmValue                   *target_range;
 	GnmValue                   *input_range;
 	gint                    i;
@@ -515,7 +798,7 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 	input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
 						     state->sheet);
 
-	gnm_solver_param_set_input (param, value_dup (input_range));
+	gnm_solver_param_set_input (param, input_range);
 
 	gnm_solver_param_set_target (param,
 				     target_range
@@ -594,29 +877,36 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 		goto out;
 	}
 
+#if 0
 	res = solver (WORKBOOK_CONTROL (state->wbcg), state->sheet, &err);
+#else
+	res = run_solver (state, param);
+#endif
+
 	workbook_recalc (state->sheet->workbook);
 
 	if (res != NULL) {
+		SolverResults *oldres;
 		/* WARNING : The dialog may be deleted by the reports
 		 * solver_reporting will return FALSE if state is gone and cleared */
-		if (solver_reporting (state, res) &&
-		    res->status == SolverOptimal &&
+		if (0 &&
+		    solver_reporting (state, oldres) &&
+		    oldres->status == SolverOptimal &&
 		    param->options.add_scenario)
-			solver_add_scenario (state, res,
+			solver_add_scenario (state, oldres,
 					     param->options.scenario_name);
 
-		solver_results_free (res);
-	} else
+		g_object_unref (res);
+	} else if (err) {
 		go_gtk_notice_nonmodal_dialog
 			(GTK_WINDOW (state->dialog),
 			 &(state->warning_dialog),
 			 GTK_MESSAGE_ERROR,
-			 err ? err->message : _("Unknown error."));
+			 err->message);
+	}
 
  out:
 	value_release (target_range);
-	value_release (input_range);
 	if (err)
 		g_error_free (err);
 }
@@ -970,7 +1260,7 @@ dialog_solver (WBCGtk *wbcg, Sheet *sheet)
 	if (gnumeric_dialog_raise_if_exists (wbcg, SOLVER_KEY))
 		return;
 
-	state                 = g_new (SolverState, 1);
+	state                 = g_new0 (SolverState, 1);
 	state->wbcg           = wbcg;
 	state->sheet          = sheet;
 	state->warning_dialog = NULL;
diff --git a/src/gnm-marshalers.list b/src/gnm-marshalers.list
index 24275a0..2fff383 100644
--- a/src/gnm-marshalers.list
+++ b/src/gnm-marshalers.list
@@ -22,6 +22,7 @@
 #   BOOL        deprecated alias for BOOLEAN
 BOOLEAN:OBJECT
 BOOLEAN:POINTER
+BOOLEAN:OBJECT,POINTER
 INT:INT
 POINTER:INT,INT
 POINTER:VOID
diff --git a/src/gnm-plugin.c b/src/gnm-plugin.c
index b41c8e5..86625a0 100644
--- a/src/gnm-plugin.c
+++ b/src/gnm-plugin.c
@@ -8,6 +8,7 @@
 
 #include <gnumeric-config.h>
 #include "gutils.h"
+#include <tools/gnm-solver.h>
 #include "func.h"
 #include "gnm-plugin.h"
 #include "gnumeric-gconf.h"
@@ -19,6 +20,7 @@
 #include <string.h>
 
 #define CXML2C(s) ((char const *)(s))
+#define CC2XML(s) ((xmlChar const *)(s))
 
 typedef GOPluginServiceSimpleClass	PluginServiceFunctionGroupClass;
 struct _PluginServiceFunctionGroup {
@@ -260,9 +262,9 @@ GSF_CLASS (PluginServiceFunctionGroup, plugin_service_function_group,
 /*
  * PluginServiceUI
  */
-typedef GOPluginServiceClass PluginServiceUIClass;
+typedef GOPluginServiceSimpleClass PluginServiceUIClass;
 struct _PluginServiceUI {
-	GOPluginServiceSimple	base;
+	GOPluginServiceSimple base;
 
 	char *file_name;
 	GSList *actions;
@@ -467,6 +469,157 @@ plugin_service_ui_class_init (GObjectClass *gobject_class)
 
 GSF_CLASS (PluginServiceUI, plugin_service_ui,
            plugin_service_ui_class_init, plugin_service_ui_init,
+           GO_TYPE_PLUGIN_SERVICE_SIMPLE)
+
+/****************************************************************************/
+
+/*
+ * PluginServiceSolver
+ */
+typedef GOPluginServiceClass PluginServiceSolverClass;
+struct _PluginServiceSolver {
+	GOPluginService base;
+
+	GnmSolverFactory *factory;
+
+	PluginServiceSolverCallbacks cbs;
+};
+
+static GnmSolver *
+cb_load_and_create (GnmSolverFactory *factory, SolverParameters *param)
+{
+	PluginServiceSolver *ssol =
+		g_object_get_data (G_OBJECT (factory), "ssol");
+	GOErrorInfo *ignored_error = NULL;
+
+	go_plugin_service_load (GO_PLUGIN_SERVICE (ssol), &ignored_error);
+	if (ignored_error != NULL) {
+		go_error_info_print (ignored_error);
+		go_error_info_free (ignored_error);
+		return NULL;
+	}
+
+	return ssol->cbs.creator (factory, param);
+}
+
+static void
+plugin_service_solver_init (PluginServiceSolver *ssol)
+{
+	GO_PLUGIN_SERVICE (ssol)->cbs_ptr = &ssol->cbs;
+	ssol->factory = NULL;
+	ssol->cbs.creator = NULL;
+}
+
+static void
+plugin_service_solver_finalize (GObject *obj)
+{
+	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (obj);
+	GObjectClass *parent_class;
+
+	if (ssol->factory)
+		g_object_unref (ssol->factory);
+
+	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
+	parent_class->finalize (obj);
+}
+
+static void
+plugin_service_solver_read_xml (GOPluginService *service, xmlNode *tree,
+				GOErrorInfo **ret_error)
+{
+	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
+	xmlChar *s_id, *s_name, *s_type;
+	SolverProblemType type = SolverLPModel;
+	xmlNode *information_node;
+
+	GO_INIT_RET_ERROR_INFO (ret_error);
+
+	s_type = go_xml_node_get_cstr (tree, "problem_type");
+	if (s_type && strcmp (CXML2C (s_type), "mip") == 0)
+		type = SolverLPModel;
+	else {
+		*ret_error = go_error_info_new_str (_("Invalid solver problem type."));
+		return;
+	}
+	xmlFree (s_type);
+
+	s_id = go_xml_node_get_cstr (tree, "id");
+
+	s_name = NULL;
+	information_node = go_xml_get_child_by_name (tree, "information");
+	if (information_node != NULL) {
+		xmlNode *node =
+			go_xml_get_child_by_name_by_lang (information_node,
+							  "description");
+		if (node != NULL) {
+			s_name = xmlNodeGetContent (node);
+		}
+	}
+
+	if (!s_id || !s_name) {
+		*ret_error = go_error_info_new_str (_("Missing fields in plugin file"));
+	} else {
+		ssol->factory = gnm_solver_factory_new (CXML2C (s_id),
+							CXML2C (s_name),
+							type,
+							cb_load_and_create);
+		g_object_set_data (G_OBJECT (ssol->factory), "ssol", ssol);
+	}
+	xmlFree (s_id);
+	xmlFree (s_name);
+	if (*ret_error)
+		return;
+
+	/* More? */
+}
+
+static void
+plugin_service_solver_activate (GOPluginService *service, GOErrorInfo **ret_error)
+{
+	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
+	char const *textdomain;
+
+	GO_INIT_RET_ERROR_INFO (ret_error);
+
+	textdomain = go_plugin_get_textdomain (service->plugin);
+
+	gnm_solver_db_register (ssol->factory);
+
+	service->is_active = TRUE;
+}
+
+static void
+plugin_service_solver_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
+{
+	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
+
+	GO_INIT_RET_ERROR_INFO (ret_error);
+	gnm_solver_db_unregister (ssol->factory);
+	service->is_active = FALSE;
+}
+
+static char *
+plugin_service_solver_get_description (GOPluginService *service)
+{
+	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
+	return g_strdup_printf (_("Solver Algorithm %s"),
+				ssol->factory->name);
+}
+
+static void
+plugin_service_solver_class_init (GObjectClass *gobject_class)
+{
+	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);
+
+	gobject_class->finalize = plugin_service_solver_finalize;
+	plugin_service_class->read_xml = plugin_service_solver_read_xml;
+	plugin_service_class->activate = plugin_service_solver_activate;
+	plugin_service_class->deactivate = plugin_service_solver_deactivate;
+	plugin_service_class->get_description = plugin_service_solver_get_description;
+}
+
+GSF_CLASS (PluginServiceSolver, plugin_service_solver,
+           plugin_service_solver_class_init, plugin_service_solver_init,
            GO_TYPE_PLUGIN_SERVICE)
 
 /****************************************************************************/
@@ -642,6 +795,38 @@ gnm_plugin_loader_module_load_service_ui (GOPluginLoader *loader,
 		"loader_data", loader_data, ui_loader_data_free);
 }
 
+static void
+gnm_plugin_loader_module_load_service_solver (GOPluginLoader *loader,
+					      GOPluginService *service,
+					      GOErrorInfo **ret_error)
+{
+	GnmPluginLoaderModule *loader_module =
+		GNM_PLUGIN_LOADER_MODULE (loader);
+	PluginServiceSolverCallbacks *cbs;
+	char *symname;
+	GnmSolverCreator creator;
+
+	g_return_if_fail (IS_GNM_PLUGIN_SERVICE_SOLVER (service));
+
+	GO_INIT_RET_ERROR_INFO (ret_error);
+
+	symname = g_strconcat (go_plugin_service_get_id (service),
+			       "_solver_factory",
+			       NULL);
+	g_module_symbol (loader_module->handle, symname, (gpointer)&creator);
+	g_free (symname);
+
+	if (!creator) {
+		*ret_error = go_error_info_new_printf (
+			_("Module file \"%s\" has invalid format."),
+			loader_module->module_file_name);
+		return;
+	}
+
+	cbs = go_plugin_service_get_cbs (service);
+	cbs->creator = creator;
+}
+
 static gboolean
 gplm_service_load (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
 {
@@ -649,6 +834,8 @@ gplm_service_load (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
 		gnm_plugin_loader_module_load_service_function_group (l, s, err);
 	else if (IS_GNM_PLUGIN_SERVICE_UI (s))
 		gnm_plugin_loader_module_load_service_ui (l, s, err);
+	else if (IS_GNM_PLUGIN_SERVICE_SOLVER (s))
+		gnm_plugin_loader_module_load_service_solver (l, s, err);
 	else
 		return FALSE;
 	return TRUE;
@@ -663,6 +850,10 @@ gplm_service_unload (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
 	} else if (IS_GNM_PLUGIN_SERVICE_UI (s)) {
 		PluginServiceUICallbacks *cbs = go_plugin_service_get_cbs (s);
 		cbs->plugin_func_exec_action = NULL;
+	} else if (IS_GNM_PLUGIN_SERVICE_SOLVER (s)) {
+		PluginServiceSolverCallbacks *cbs =
+			go_plugin_service_get_cbs (s);
+		cbs->creator = NULL;
 	} else
 		return FALSE;
 	return TRUE;
diff --git a/src/gnm-plugin.h b/src/gnm-plugin.h
index 92de539..ee13ee3 100644
--- a/src/gnm-plugin.h
+++ b/src/gnm-plugin.h
@@ -5,6 +5,7 @@
 #include <gnumeric.h>
 #include <goffice/goffice.h>
 #include <goffice/app/module-plugin-defs.h>
+#include <tools/gnm-solver.h>
 #include <gmodule.h>
 #include <libxml/tree.h>
 #include <gsf/gsf.h>
@@ -47,6 +48,16 @@ typedef struct {
 	void (*handler) (GnmAction const *action, WorkbookControl *wbc);
 } ModulePluginUIActions;
 
+#define GNM_PLUGIN_SERVICE_SOLVER_TYPE  (plugin_service_solver_get_type ())
+#define GNM_PLUGIN_SERVICE_SOLVER(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_PLUGIN_SERVICE_SOLVER_TYPE, PluginServiceSolver))
+#define IS_GNM_PLUGIN_SERVICE_SOLVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_PLUGIN_SERVICE_SOLVER_TYPE))
+
+GType plugin_service_solver_get_type (void);
+typedef struct _PluginServiceSolver PluginServiceSolver;
+typedef struct {
+	GnmSolverCreator creator;
+} PluginServiceSolverCallbacks;
+
 /**************************************************************************/
 #define GNM_PLUGIN_MODULE_HEADER					\
 G_MODULE_EXPORT GOPluginModuleDepend const go_plugin_depends [] = {	\
diff --git a/src/libgnumeric.c b/src/libgnumeric.c
index ef3f477..7e456d3 100644
--- a/src/libgnumeric.c
+++ b/src/libgnumeric.c
@@ -254,6 +254,8 @@ gnm_init (void)
 		&plugin_service_function_group_get_type);
 	go_plugin_service_define ("ui",
 		&plugin_service_ui_get_type);
+	go_plugin_service_define ("solver",
+		&plugin_service_solver_get_type);
 	go_plugin_loader_module_register_version ("gnumeric", GNM_VERSION_FULL);
 
 	g_object_new (GNM_APP_TYPE, NULL);
diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am
index eaa1f76..832312e 100644
--- a/src/tools/Makefile.am
+++ b/src/tools/Makefile.am
@@ -47,6 +47,8 @@ libtools_la_SOURCES =					\
 	fill-series.h					\
 	filter.h					\
 	filter.c					\
+	gnm-solver.c					\
+	gnm-solver.h					\
 	goal-seek.c					\
 	goal-seek.h					\
 	scenarios.c					\
diff --git a/src/tools/gnm-solver.c b/src/tools/gnm-solver.c
new file mode 100644
index 0000000..b318b2e
--- /dev/null
+++ b/src/tools/gnm-solver.c
@@ -0,0 +1,665 @@
+#include <gnumeric-config.h>
+#include "gnumeric.h"
+#include "value.h"
+#include "cell.h"
+#include "sheet.h"
+#include "workbook.h"
+#include "ranges.h"
+#include "gnm-solver.h"
+#include "workbook-view.h"
+#include "workbook-control.h"
+#include "gnm-marshalers.h"
+#include <gsf/gsf-impl-utils.h>
+#include <gsf/gsf-output-stdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+
+/* ------------------------------------------------------------------------- */
+
+GType
+gnm_solver_status_get_type (void)
+{
+	static GType etype = 0;
+	if (etype == 0) {
+		static GEnumValue const values[] = {
+			{ GNM_SOLVER_STATUS_READY,
+			  (char *)"GNM_SOLVER_STATUS_READY",
+			  (char *)"ready"
+			},
+			{ GNM_SOLVER_STATUS_PREPARING,
+			  (char *)"GNM_SOLVER_STATUS_PREPARING",
+			  (char *)"preparing"
+			},
+			{ GNM_SOLVER_STATUS_PREPARED,
+			  (char *)"GNM_SOLVER_STATUS_PREPARED",
+			  (char *)"prepared"
+			},
+			{ GNM_SOLVER_STATUS_RUNNING,
+			  (char *)"GNM_SOLVER_STATUS_RUNNING",
+			  (char *)"running"
+			},
+			{ GNM_SOLVER_STATUS_DONE,
+			  (char *)"GNM_SOLVER_STATUS_DONE",
+			  (char *)"done"
+			},
+			{ GNM_SOLVER_STATUS_ERROR,
+			  (char *)"GNM_SOLVER_STATUS_ERROR",
+			  (char *)"error"
+			},
+			{ GNM_SOLVER_STATUS_CANCELLED,
+			  (char *)"GNM_SOLVER_STATUS_CANCELLED",
+			  (char *)"cancelled"
+			},
+			{ 0, NULL, NULL }
+		};
+		etype = g_enum_register_static ("GnmStatus", values);
+	}
+	return etype;
+}
+
+/* ------------------------------------------------------------------------- */
+
+enum {
+	SOL_SIG_PREPARE,
+	SOL_SIG_START,
+	SOL_SIG_STOP,
+	SOL_SIG_LAST
+};
+
+static guint solver_signals[SOL_SIG_LAST] = { 0 };
+
+enum {
+	SOL_PROP_0,
+	SOL_PROP_STATUS,
+	SOL_PROP_PARAMS,
+	SOL_PROP_RESULT
+};
+
+static GObjectClass *gnm_solver_parent_class;
+
+static void
+gnm_solver_dispose (GObject *obj)
+{
+	GnmSolver *sol = GNM_SOLVER (obj);
+
+	if (sol->status == GNM_SOLVER_STATUS_RUNNING) {
+		gboolean ok = gnm_solver_stop (sol, NULL);
+		if (ok) {
+			g_warning ("Failed to stop solver -- now what?");
+		}
+	}
+
+	if (sol->result) {
+		g_object_unref (sol->result);
+		sol->result = NULL;
+	}
+
+	gnm_solver_parent_class->dispose (obj);
+}
+
+static void
+gnm_solver_get_property (GObject *object, guint property_id,
+			 GValue *value, GParamSpec *pspec)
+{
+	GnmSolver *sol = (GnmSolver *)object;
+
+	switch (property_id) {
+	case SOL_PROP_STATUS:
+		g_value_set_enum (value, sol->status);
+		break;
+
+	case SOL_PROP_PARAMS:
+		g_value_set_pointer (value, sol->params);
+		break;
+
+	case SOL_PROP_RESULT:
+		g_value_set_object (value, sol->result);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+gnm_solver_set_property (GObject *object, guint property_id,
+			 GValue const *value, GParamSpec *pspec)
+{
+	GnmSolver *sol = (GnmSolver *)object;
+
+	switch (property_id) {
+	case SOL_PROP_STATUS:
+		gnm_solver_set_status (sol, g_value_get_enum (value));
+		break;
+
+	case SOL_PROP_PARAMS:
+		sol->params = g_value_peek_pointer (value);
+		break;
+
+	case SOL_PROP_RESULT:
+		if (sol->result) g_object_unref (sol->result);
+		sol->result = g_value_dup_object (value);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+gboolean
+gnm_solver_prepare (GnmSolver *sol, WorkbookControl *wbc, GError **err)
+{
+	gboolean res;
+
+	g_return_val_if_fail (GNM_IS_SOLVER (sol), FALSE);
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_READY, FALSE);
+
+	g_signal_emit (sol, solver_signals[SOL_SIG_PREPARE], 0, wbc, err, &res);
+	return res;
+}
+
+gboolean
+gnm_solver_start (GnmSolver *sol, WorkbookControl *wbc, GError **err)
+{
+	gboolean res;
+
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_READY ||
+			      sol->status == GNM_SOLVER_STATUS_PREPARED,
+			      FALSE);
+
+	if (sol->status == GNM_SOLVER_STATUS_READY) {
+		res = gnm_solver_prepare (sol, wbc, err);
+		if (!res)
+			return FALSE;
+	}
+
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_PREPARED, FALSE);
+
+	g_signal_emit (sol, solver_signals[SOL_SIG_START], 0, wbc, err, &res);
+	return res;
+}
+
+gboolean
+gnm_solver_stop (GnmSolver *sol, GError **err)
+{
+	gboolean res;
+
+	g_return_val_if_fail (GNM_IS_SOLVER (sol), FALSE);
+
+	g_signal_emit (sol, solver_signals[SOL_SIG_STOP], 0, err, &res);
+	return res;
+}
+
+void
+gnm_solver_store_result (GnmSolver *sol)
+{
+	GnmValue const *vinput;
+	GnmSheetRange sr;
+	int h, w, x, y;
+	GnmSolverResult const *result;
+	GnmValue const *solution;
+
+	g_return_if_fail (GNM_IS_SOLVER (sol));
+	g_return_if_fail (sol->result != NULL);
+	g_return_if_fail (sol->result->solution);
+
+	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);
+
+	result = sol->result;
+	switch (result->quality) {
+	case GNM_SOLVER_RESULT_FEASIBLE:
+	case GNM_SOLVER_RESULT_OPTIMAL:
+		solution = result->solution;
+		break;
+	default:
+	case GNM_SOLVER_RESULT_NONE:
+	case GNM_SOLVER_RESULT_INFEASIBLE:
+	case GNM_SOLVER_RESULT_UNBOUNDED:
+		solution = NULL;
+		break;
+	}
+
+	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);
+		}
+	}
+}
+
+gboolean
+gnm_solver_finished (GnmSolver *sol)
+{
+	g_return_val_if_fail (GNM_IS_SOLVER (sol), TRUE);
+
+	switch (sol->status) {
+
+	case GNM_SOLVER_STATUS_READY:
+	case GNM_SOLVER_STATUS_PREPARING:
+	case GNM_SOLVER_STATUS_PREPARED:
+	case GNM_SOLVER_STATUS_RUNNING:
+		return FALSE;
+	case GNM_SOLVER_STATUS_DONE:
+	default:
+	case GNM_SOLVER_STATUS_ERROR:
+	case GNM_SOLVER_STATUS_CANCELLED:
+		return TRUE;
+	}
+}
+
+void
+gnm_solver_set_status (GnmSolver *solver, GnmSolverStatus status)
+{
+	if (status == solver->status)
+		return;
+
+	solver->status = status;
+	g_object_notify (G_OBJECT (solver), "status");
+}
+
+gboolean
+gnm_solver_saveas (GnmSolver *solver, WorkbookControl *wbc,
+		   GOFileSaver *fs,
+		   const char *template, char **filename,
+		   GError **err)
+{
+	int fd;
+	GsfOutput *output;
+	FILE *file;
+	GOIOContext *io_context;
+	gboolean ok;
+	WorkbookView *wbv = wb_control_view (wbc);
+
+	fd = g_file_open_tmp (template, filename, err);
+	if (fd == -1) {
+		g_set_error (err, G_FILE_ERROR, 0,
+			     _("Failed to create file for linear program"));
+		return FALSE;
+	}
+
+	file = fdopen (fd, "wb");
+	if (!file) {
+		/* This shouldn't really happen.  */
+		close (fd);
+		g_set_error (err, G_FILE_ERROR, 0,
+			     _("Failed to create linear program file"));
+		return FALSE;
+	}
+
+	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);
+	ok = !go_io_error_occurred (io_context);
+	g_object_unref (io_context);
+	g_object_unref (output);
+
+	if (!ok) {
+		g_set_error (err, G_FILE_ERROR, 0,
+			     _("Failed to save linear program"));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+gnm_solver_class_init (GObjectClass *object_class)
+{
+	gnm_solver_parent_class = g_type_class_peek_parent (object_class);
+
+	object_class->dispose = gnm_solver_dispose;
+	object_class->set_property = gnm_solver_set_property;
+	object_class->get_property = gnm_solver_get_property;
+
+        g_object_class_install_property (object_class, SOL_PROP_STATUS,
+		 g_param_spec_enum ("status", _("status"),
+				    _("The solver's current status"),
+				    GNM_SOLVER_STATUS_TYPE,
+				    GNM_SOLVER_STATUS_READY,
+				    GSF_PARAM_STATIC |
+				    G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class, SOL_PROP_PARAMS,
+		 g_param_spec_pointer ("params", _("Parameters"),
+				       _("Solver parameters"),
+				       GSF_PARAM_STATIC |
+				       G_PARAM_CONSTRUCT_ONLY |
+				       G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class, SOL_PROP_RESULT,
+		 g_param_spec_object ("result", _("Result"),
+				      _("Current best feasible result"),
+				      GNM_SOLVER_RESULT_TYPE,
+				      GSF_PARAM_STATIC |
+				      G_PARAM_READWRITE));
+
+	solver_signals[SOL_SIG_PREPARE] =
+		g_signal_new ("prepare",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GnmSolverClass, prepare),
+			      NULL, NULL,
+			      gnm__BOOLEAN__OBJECT_POINTER,
+			      G_TYPE_BOOLEAN, 2,
+			      G_TYPE_OBJECT,
+			      G_TYPE_POINTER);
+
+	solver_signals[SOL_SIG_START] =
+		g_signal_new ("start",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GnmSolverClass, start),
+			      NULL, NULL,
+			      gnm__BOOLEAN__OBJECT_POINTER,
+			      G_TYPE_BOOLEAN, 2,
+			      G_TYPE_OBJECT,
+			      G_TYPE_POINTER);
+
+	solver_signals[SOL_SIG_STOP] =
+		g_signal_new ("stop",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GnmSolverClass, stop),
+			      NULL, NULL,
+			      gnm__BOOLEAN__POINTER,
+			      G_TYPE_BOOLEAN, 1,
+			      G_TYPE_POINTER);
+}
+
+GSF_CLASS (GnmSolver, gnm_solver,
+	   &gnm_solver_class_init, NULL, G_TYPE_OBJECT)
+
+/* ------------------------------------------------------------------------- */
+
+static GObjectClass *gnm_solver_result_parent_class;
+
+static void
+gnm_solver_result_finalize (GObject *obj)
+{
+	GnmSolverResult *r = GNM_SOLVER_RESULT (obj);
+	value_release (r->solution);
+	gnm_solver_result_parent_class->finalize (obj);
+}
+
+static void
+gnm_solver_result_class_init (GObjectClass *object_class)
+{
+	gnm_solver_result_parent_class =
+		g_type_class_peek_parent (object_class);
+
+	object_class->finalize = gnm_solver_result_finalize;
+}
+
+GSF_CLASS (GnmSolverResult, gnm_solver_result,
+	   &gnm_solver_result_class_init, NULL, G_TYPE_OBJECT)
+
+/* ------------------------------------------------------------------------- */
+
+static GObjectClass *gnm_sub_solver_parent_class;
+
+void
+gnm_sub_solver_clear (GnmSubSolver *subsol)
+{
+	int i;
+
+	if (subsol->child_watch) {
+		g_source_remove (subsol->child_watch);
+		subsol->child_watch = 0;
+		subsol->child_exit = NULL;
+		subsol->exit_data = NULL;
+	}
+
+	if (subsol->child_pid) {
+		kill (subsol->child_pid, SIGKILL);
+		g_spawn_close_pid (subsol->child_pid);
+		subsol->child_pid = (GPid)0;
+	}
+
+	for (i = 0; i <= 2; i++) {
+		if (subsol->channel_watches[i]) {
+			g_source_remove (subsol->channel_watches[i]);
+			subsol->channel_watches[i] = 0;
+		}
+		if (subsol->channels[i]) {
+			g_io_channel_unref (subsol->channels[i]);
+			subsol->channels[i] = NULL;
+		}
+		if (subsol->fd[i] != -1) {
+			close (subsol->fd[i]);
+			subsol->fd[i] = -1;
+		}
+	}
+
+	if (subsol->program_filename) {
+		g_unlink (subsol->program_filename);
+		g_free (subsol->program_filename);
+		subsol->program_filename = NULL;
+	}
+}
+
+static void
+gnm_sub_solver_dispose (GObject *obj)
+{
+	GnmSubSolver *subsol = GNM_SUB_SOLVER (obj);
+
+	gnm_sub_solver_clear (subsol);
+
+	gnm_sub_solver_parent_class->dispose (obj);
+}
+
+static void
+gnm_sub_solver_init (GnmSubSolver *subsol)
+{
+	int i;
+
+	for (i = 0; i <= 2; i++)
+		subsol->fd[i] = -1;
+}
+
+static void
+cb_child_exit (GPid pid, gint status, GnmSubSolver *subsol)
+{
+	subsol->child_watch = 0;
+	if (subsol->child_exit)
+		subsol->child_exit (pid, status, subsol->exit_data);
+}
+
+gboolean
+gnm_sub_solver_spawn (GnmSubSolver *subsol,
+		      char **argv,
+		      GSpawnChildSetupFunc child_setup, gpointer setup_data,
+		      GChildWatchFunc child_exit, gpointer exit_data,
+		      GIOFunc io_stdout, gpointer stdout_data,
+		      GIOFunc io_stderr, gpointer stderr_data,
+		      GError **err)
+{
+	GnmSolver *sol = GNM_SOLVER (subsol);
+	gboolean ok;
+	GSpawnFlags spflags = G_SPAWN_DO_NOT_REAP_CHILD;
+	int fd;
+
+	g_return_val_if_fail (subsol->child_watch == 0, FALSE);
+	g_return_val_if_fail (sol->status == GNM_SOLVER_STATUS_PREPARED, FALSE);
+
+	if (!g_path_is_absolute (argv[0]))
+		spflags |= G_SPAWN_SEARCH_PATH;
+
+	ok = g_spawn_async_with_pipes
+		(g_get_home_dir (),  /* PWD */
+		 argv,
+		 NULL, /* environment */
+		 spflags,
+		 child_setup, setup_data,
+		 &subsol->child_pid,
+		 NULL,			/* stdin */
+		 &subsol->fd[1],	/* stdout */
+		 &subsol->fd[2],	/* stderr */
+		 err);
+	if (!ok)
+		goto fail;
+
+	subsol->child_exit = child_exit;
+	subsol->exit_data = exit_data;
+	subsol->child_watch =
+		g_child_watch_add (subsol->child_pid,
+				   (GChildWatchFunc)cb_child_exit, subsol);
+
+	subsol->io_funcs[1] = io_stdout;
+	subsol->io_funcs_data[1] = stdout_data;
+	subsol->io_funcs[2] = io_stderr;
+	subsol->io_funcs_data[2] = stderr_data;
+
+	for (fd = 1; fd <= 2; fd++) {
+		GIOFlags ioflags;
+
+		if (subsol->io_funcs[fd] == NULL)
+			continue;
+
+		/*
+		 * Despite the name these are documented to work on Win32.
+		 * Let us hope that is actually true.
+		 */
+		subsol->channels[fd] = g_io_channel_unix_new (subsol->fd[fd]);
+		ioflags = g_io_channel_get_flags (subsol->channels[fd]);
+		g_io_channel_set_flags (subsol->channels[fd],
+					ioflags | G_IO_FLAG_NONBLOCK,
+					NULL);
+		subsol->channel_watches[fd] =
+			g_io_add_watch (subsol->channels[fd],
+					G_IO_IN,
+					subsol->io_funcs[fd],
+					subsol->io_funcs_data[fd]);
+	}
+
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_RUNNING);
+	return TRUE;
+
+fail:
+	gnm_sub_solver_clear (subsol);
+	gnm_solver_set_status (sol, GNM_SOLVER_STATUS_ERROR);
+	return FALSE;
+}
+
+void
+gnm_sub_solver_flush (GnmSubSolver *subsol)
+{
+	int fd;
+
+	for (fd = 1; fd <= 2; fd++) {
+		if (subsol->io_funcs[fd] == NULL)
+			continue;
+
+		subsol->io_funcs[fd] (subsol->channels[fd],
+				      G_IO_IN,
+				      subsol->io_funcs_data[fd]);
+	}
+}
+
+static void
+gnm_sub_solver_class_init (GObjectClass *object_class)
+{
+	gnm_sub_solver_parent_class = g_type_class_peek_parent (object_class);
+
+	object_class->dispose = gnm_sub_solver_dispose;
+}
+
+GSF_CLASS (GnmSubSolver, gnm_sub_solver,
+	   gnm_sub_solver_class_init, gnm_sub_solver_init, GNM_SOLVER_TYPE)
+
+/* ------------------------------------------------------------------------- */
+
+static GObjectClass *gnm_solver_factory_parent_class;
+
+static void
+gnm_solver_factory_dispose (GObject *obj)
+{
+	GnmSolverFactory *factory = GNM_SOLVER_FACTORY (obj);
+
+	g_free (factory->id);
+	g_free (factory->name);
+
+	gnm_solver_factory_parent_class->dispose (obj);
+}
+
+static void
+gnm_solver_factory_class_init (GObjectClass *object_class)
+{
+	gnm_solver_factory_parent_class =
+		g_type_class_peek_parent (object_class);
+
+	object_class->dispose = gnm_solver_factory_dispose;
+}
+
+GSF_CLASS (GnmSolverFactory, gnm_solver_factory,
+	   gnm_solver_factory_class_init, NULL, G_TYPE_OBJECT)
+
+
+static GSList *solvers;
+
+GSList *
+gnm_solver_db_get (void)
+{
+	return solvers;
+}
+
+GnmSolverFactory *
+gnm_solver_factory_new (const char *id,
+			const char *name,
+			SolverModelType type,
+			GnmSolverCreator creator)
+{
+	GnmSolverFactory *res;
+
+	g_return_val_if_fail (id != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (creator != NULL, NULL);
+
+	res = g_object_new (GNM_SOLVER_FACTORY_TYPE, NULL);
+	res->id = g_strdup (id);
+	res->name = g_strdup (name);
+	res->type = type;
+	res->creator = creator;
+	return res;
+}
+
+GnmSolver *
+gnm_solver_factory_create (GnmSolverFactory *factory,
+			   SolverParameters *param)
+{
+	g_return_val_if_fail (GNM_IS_SOLVER_FACTORY (factory), NULL);
+	return factory->creator (factory, param);
+}
+
+void
+gnm_solver_db_register (GnmSolverFactory *factory)
+{
+	g_printerr ("Registering %s\n", factory->id);
+	g_object_ref (factory);
+	solvers = g_slist_prepend (solvers, factory);
+}
+
+void
+gnm_solver_db_unregister (GnmSolverFactory *factory)
+{
+	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
new file mode 100644
index 0000000..44f2dee
--- /dev/null
+++ b/src/tools/gnm-solver.h
@@ -0,0 +1,184 @@
+#ifndef _TOOLS_GNM_SOLVER_H_
+#define _TOOLS_GNM_SOLVER_H_
+
+#include <glib-object.h>
+#include <gnumeric.h>
+#include <solver.h>
+
+G_BEGIN_DECLS
+
+/* ------------------------------------------------------------------------- */
+
+typedef enum {
+	GNM_SOLVER_RESULT_NONE,
+	GNM_SOLVER_RESULT_FEASIBLE,
+	GNM_SOLVER_RESULT_OPTIMAL,
+	GNM_SOLVER_RESULT_INFEASIBLE,
+	GNM_SOLVER_RESULT_UNBOUNDED
+} GnmSolverResultQuality;
+
+
+GType gnm_solver_status_get_type (void);
+#define GNM_SOLVER_STATUS_TYPE (gnm_solver_status_get_type ())
+
+typedef enum {
+	GNM_SOLVER_STATUS_READY,
+	GNM_SOLVER_STATUS_PREPARING,
+	GNM_SOLVER_STATUS_PREPARED,
+	GNM_SOLVER_STATUS_RUNNING,
+	GNM_SOLVER_STATUS_DONE,
+	GNM_SOLVER_STATUS_ERROR,
+	GNM_SOLVER_STATUS_CANCELLED
+} GnmSolverStatus;
+
+/* ------------------------------------------------------------------------- */
+
+#define GNM_SOLVER_RESULT_TYPE   (gnm_solver_result_get_type ())
+#define GNM_SOLVER_RESULT(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SOLVER_RESULT_TYPE, GnmSolverResult))
+
+typedef struct {
+	GObject parent;
+
+	GnmSolverResultQuality quality;
+	gnm_float value;
+	GnmValue *solution;
+} GnmSolverResult;
+
+typedef struct {
+	GObjectClass parent_class;
+} GnmSolverResultClass;
+
+GType gnm_solver_result_get_type  (void);
+
+/* ------------------------------------------------------------------------- */
+/* Generic Solver class. */
+
+#define GNM_SOLVER_TYPE        (gnm_solver_get_type ())
+#define GNM_SOLVER(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SOLVER_TYPE, GnmSolver))
+#define GNM_SOLVER_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST ((k), GNM_SOLVER_TYPE, GnmSolverClass))
+#define GNM_IS_SOLVER(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_SOLVER_TYPE))
+
+typedef struct {
+	GObject parent;
+
+	GnmSolverStatus status;
+	SolverParameters *params;
+	GnmSolverResult *result;
+} GnmSolver;
+
+typedef struct {
+	GObjectClass parent_class;
+
+	gboolean (*prepare) (GnmSolver *solver,
+			     WorkbookControl *wbc, GError **err);
+	gboolean (*start) (GnmSolver *solver,
+			   WorkbookControl *wbc, GError **err);
+	gboolean (*stop) (GnmSolver *solver, GError **err);
+} GnmSolverClass;
+
+GType gnm_solver_get_type  (void);
+
+gboolean gnm_solver_prepare (GnmSolver *solver,
+			     WorkbookControl *wbc, GError **err);
+gboolean gnm_solver_start (GnmSolver *solver,
+			   WorkbookControl *wbc, GError **err);
+gboolean gnm_solver_stop (GnmSolver *solver, GError **err);
+
+void gnm_solver_set_status (GnmSolver *solver, GnmSolverStatus status);
+
+void gnm_solver_store_result (GnmSolver *solver);
+
+gboolean gnm_solver_finished (GnmSolver *solver);
+
+gboolean gnm_solver_saveas (GnmSolver *solver, WorkbookControl *wbc,
+			    GOFileSaver *fs,
+			    const char *template, char **filename,
+			    GError **err);
+
+/* ------------------------------------------------------------------------- */
+/* Solver subclass for subprocesses. */
+
+#define GNM_SUB_SOLVER_TYPE     (gnm_sub_solver_get_type ())
+#define GNM_SUB_SOLVER(o)       (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SUB_SOLVER_TYPE, GnmSubSolver))
+#define GNM_SUB_SOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GNM_SUB_SOLVER_TYPE, GnmSubSolverClass))
+#define GNM_IS_SUB_SOLVER(o)    (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_SUB_SOLVER_TYPE))
+
+typedef struct {
+	GnmSolver parent;
+
+	char *program_filename;
+
+	GPid child_pid;
+	guint child_watch;
+
+	GChildWatchFunc child_exit;
+	gpointer exit_data;
+
+	gint fd[3];
+	GIOChannel *channels[3];
+	guint channel_watches[3];
+	GIOFunc io_funcs[3];
+	gpointer io_funcs_data[3];
+} GnmSubSolver;
+
+typedef struct {
+	GnmSolverClass parent_class;
+} GnmSubSolverClass;
+
+GType gnm_sub_solver_get_type  (void);
+
+void gnm_sub_solver_clear (GnmSubSolver *subsol);
+
+gboolean gnm_sub_solver_spawn
+		(GnmSubSolver *subsol,
+		 char **argv,
+		 GSpawnChildSetupFunc child_setup, gpointer setup_data,
+		 GChildWatchFunc child_exit, gpointer exit_data,
+		 GIOFunc io_stdout, gpointer stdout_data,
+		 GIOFunc io_stderr, gpointer stderr_data,
+		 GError **err);
+
+void gnm_sub_solver_flush (GnmSubSolver *subsol);
+
+/* ------------------------------------------------------------------------- */
+
+#define GNM_SOLVER_FACTORY_TYPE        (gnm_solver_factory_get_type ())
+#define GNM_SOLVER_FACTORY(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SOLVER_FACTORY_TYPE, GnmSolverFactory))
+#define GNM_IS_SOLVER_FACTORY(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_SOLVER_FACTORY_TYPE))
+
+typedef struct GnmSolverFactory_ GnmSolverFactory;
+
+typedef GnmSolver * (*GnmSolverCreator) (GnmSolverFactory *,
+					 SolverParameters *);
+
+struct GnmSolverFactory_ {
+	GObject parent;
+
+	char *id;
+	char *name; /* Already translated */
+	SolverModelType type;
+	GnmSolverCreator creator;
+};
+
+typedef struct {
+	GObjectClass parent_class;
+} GnmSolverFactoryClass;
+
+GType gnm_solver_factory_get_type (void);
+
+GnmSolverFactory *gnm_solver_factory_new (const char *id,
+					  const char *name,
+					  SolverModelType type,
+					  GnmSolverCreator creator);
+GnmSolver *gnm_solver_factory_create (GnmSolverFactory *factory,
+				      SolverParameters *param);
+
+GSList *gnm_solver_db_get (void);
+void gnm_solver_db_register (GnmSolverFactory *factory);
+void gnm_solver_db_unregister (GnmSolverFactory *factory);
+
+/* ------------------------------------------------------------------------- */
+
+G_END_DECLS
+
+#endif /* _TOOLS_GNM_SOLVER_H_ */



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