[gnumeric] solver: use external solver.
- From: Morten Welinder <mortenw src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnumeric] solver: use external solver.
- Date: Wed, 11 Nov 2009 21:45:42 +0000 (UTC)
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]