[gnumeric] solver: improve undo/redo.



commit 7bd121851585db43ddf1ae92739d6c53022d6546
Author: Morten Welinder <terra gnome org>
Date:   Sun Nov 15 00:03:13 2009 -0500

    solver: improve undo/redo.

 NEWS                        |    2 +
 src/commands.c              |    4 +-
 src/commands.h              |    3 +-
 src/dialogs/dialog-solver.c |  303 ++++++++++++++++++++++++++-----------------
 src/expr.c                  |    2 +
 src/sheet.c                 |   10 ++
 src/sheet.h                 |    2 +
 src/tools/gnm-solver.c      |  103 ++++++++++++---
 src/tools/gnm-solver.h      |    6 +
 9 files changed, 294 insertions(+), 141 deletions(-)
---
diff --git a/NEWS b/NEWS
index 0e234d0..610c85b 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,8 @@ Morten:
 	* Use external solver program.
 	* Allow constants on solver constraints' rhs.  [#369728]
 	* Fix extreme case for R.PGAMMA.
+	* Fix solver undo/redo.
+	* Make solver parameter changes always persist.  [#440664]
 
 --------------------------------------------------------------------------
 Gnumeric 1.9.15
diff --git a/src/commands.c b/src/commands.c
index 26a0ab4..ba874ea 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -6321,7 +6321,7 @@ cmd_solver_finalize (GObject *cmd)
 }
 
 gboolean
-cmd_solver (WorkbookControl *wbc, GOUndo *undo, GOUndo *redo)
+cmd_solver (WorkbookControl *wbc, const char *txt, GOUndo *undo, GOUndo *redo)
 {
 	CmdSolver *me;
 
@@ -6332,7 +6332,7 @@ cmd_solver (WorkbookControl *wbc, GOUndo *undo, GOUndo *redo)
 
 	me->cmd.sheet = NULL;
 	me->cmd.size = 1;
-	me->cmd.cmd_descriptor = g_strdup_printf (_("Solver"));
+	me->cmd.cmd_descriptor = g_strdup (txt);
 
 	me->undo = undo;
 	me->redo = redo;
diff --git a/src/commands.h b/src/commands.h
index 8649365..c93de89 100644
--- a/src/commands.h
+++ b/src/commands.h
@@ -128,7 +128,8 @@ gboolean cmd_text_to_columns (WorkbookControl *wbc,
 			      GnmRange const *target, Sheet *target_sheet,
 			      GnmCellRegion *content);
 
-gboolean cmd_solver (WorkbookControl *wbc, GOUndo *undo, GOUndo *redo);
+gboolean cmd_solver (WorkbookControl *wbc, const char *text,
+		     GOUndo *undo, GOUndo *redo);
 
 gboolean cmd_goal_seek (WorkbookControl *wbc,
 			GnmCell *cell, GnmValue *ov, GnmValue *nv);
diff --git a/src/dialogs/dialog-solver.c b/src/dialogs/dialog-solver.c
index b28e9ba..125ef90 100644
--- a/src/dialogs/dialog-solver.c
+++ b/src/dialogs/dialog-solver.c
@@ -75,7 +75,7 @@ typedef struct {
 	GtkComboBox         *type_combo;
 	GtkComboBox         *algorithm_combo;
 	GtkTreeView         *constraint_list;
-	GnmSolverConstraint    *constr;
+	GnmSolverConstraint *constr;
 	GtkWidget           *warning_dialog;
 
 	struct {
@@ -92,6 +92,8 @@ typedef struct {
 
 	Sheet		    *sheet;
 	WBCGtk              *wbcg;
+
+	GnmSolverParameters *orig_params;
 } SolverState;
 
 
@@ -291,6 +293,8 @@ fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
 	GtkListStore *store =
 		gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
 	GSList *solvers, *l;
+	int sel = 0, i;
+	GnmSolverParameters *param =state->sheet->solver_parameters;
 
 	gtk_combo_box_set_model (state->algorithm_combo, GTK_TREE_MODEL (store));
 
@@ -301,17 +305,20 @@ fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
 			continue;
 		l = g_slist_prepend (l, entry);
 	}
-	solvers = l;
+	solvers = g_slist_reverse (l);
 
 	gtk_widget_set_sensitive (GTK_WIDGET (state->solve_button),
 				  solvers != NULL);
 	if (!solvers)
 		return FALSE;
 
-	for (l = solvers; l; l = l->next) {
+	for (l = solvers, i = 0; l; l = l->next, i++) {
 		GnmSolverFactory *factory = l->data;
 		GtkTreeIter iter;
 
+		if (param->options.algorithm == factory)
+			sel = i;
+
 		gtk_list_store_append (store, &iter);
 		gtk_list_store_set (store, &iter,
 				    0, factory->name,
@@ -320,7 +327,7 @@ fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
 	}
 	g_slist_free (solvers);
 
-	gtk_combo_box_set_active (state->algorithm_combo, 0);
+	gtk_combo_box_set_active (state->algorithm_combo, sel);
 
 	return TRUE;
 }
@@ -347,10 +354,122 @@ cb_dialog_model_type_clicked (G_GNUC_UNUSED GtkWidget *button,
 }
 
 static void
+free_state (SolverState *state)
+{
+	if (state->orig_params)
+		g_object_unref (state->orig_params);
+	g_free (state);
+}
+
+static GOUndo *
+set_params (Sheet *sheet, GnmSolverParameters *params)
+{
+	return go_undo_binary_new
+		(sheet, g_object_ref (params),
+		 (GOUndoBinaryFunc)gnm_sheet_set_solver_params,
+		 NULL, g_object_unref);
+}
+
+#define GET_BOOL_ENTRY(name_, field_)					\
+do {									\
+	GtkWidget *w_ = glade_xml_get_widget (state->gui, (name_));	\
+	param->field_ = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w_)); \
+} while (0)
+
+static void
+extract_settings (SolverState *state)
+{
+	GnmSolverParameters *param = state->sheet->solver_parameters;
+	GtkTreeIter iter;
+	GnmCell *target_cell;
+	GnmValue *target_range;
+	GnmValue *input_range;
+	GnmSolverFactory *factory = NULL;
+	gboolean dual_program;
+
+	target_range = gnm_expr_entry_parse_as_value (state->target_entry,
+						      state->sheet);
+	input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
+						     state->sheet);
+
+	gnm_solver_param_set_input (param, input_range);
+
+	gnm_solver_param_set_target (param,
+				     target_range
+				     ? &target_range->v_range.cell.a
+				     : NULL);
+	target_cell = gnm_solver_param_get_target_cell (param);
+
+	param->problem_type =
+		gnumeric_glade_group_value (state->gui, problem_type_group);
+	param->options.model_type =
+		gnumeric_glade_group_value (state->gui, model_type_group);
+
+	gtk_combo_box_get_active_iter (state->algorithm_combo, &iter);
+	gtk_tree_model_get (gtk_combo_box_get_model (state->algorithm_combo),
+			    &iter, 1, &factory, -1);
+	param->options.algorithm = factory;
+
+	param->options.automatic_scaling = gtk_toggle_button_get_active
+		(GTK_TOGGLE_BUTTON (glade_xml_get_widget
+				    (state->gui, "autoscale_button")));
+
+	param->options.max_iter = gtk_spin_button_get_value
+		(GTK_SPIN_BUTTON (state->max_iter_entry));
+	param->options.max_time_sec = gtk_spin_button_get_value
+		(GTK_SPIN_BUTTON (state->max_time_entry));
+
+	GET_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
+	GET_BOOL_ENTRY ("all_int_button", options.assume_discrete);
+	GET_BOOL_ENTRY ("answer", options.answer_report);
+	GET_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
+	GET_BOOL_ENTRY ("limits", options.limits_report);
+	GET_BOOL_ENTRY ("performance", options.performance_report);
+	GET_BOOL_ENTRY ("program", options.program_report);
+
+	g_free (param->options.scenario_name);
+	param->options.scenario_name = g_strdup
+		(gtk_entry_get_text (GTK_ENTRY (state->scenario_name_entry)));
+
+	GET_BOOL_ENTRY ("optimal_scenario", options.add_scenario);
+
+	dual_program = FALSE;
+	param->options.dual_program_report = dual_program;
+
+	value_release (target_range);
+}
+
+#undef GET_BOOL_ENTRY
+
+static void
+check_for_changed_options (SolverState *state)
+{
+	Sheet *sheet = state->sheet;
+
+	if (!gnm_solver_param_equal (sheet->solver_parameters,
+				     state->orig_params)) {
+		GOUndo *undo = set_params (sheet, state->orig_params);
+		GOUndo *redo = set_params (sheet, sheet->solver_parameters);
+		cmd_solver (WORKBOOK_CONTROL (state->wbcg),
+			    _("Changing solver parameters"),
+			    undo, redo);
+
+		g_object_unref (state->orig_params);
+		state->orig_params =
+			gnm_solver_param_dup (sheet->solver_parameters,
+					      sheet);
+	}
+}
+
+static void
 cb_dialog_solver_destroy (SolverState *state)
 {
 	g_return_if_fail (state != NULL);
 
+	extract_settings (state);
+
+	check_for_changed_options (state);
+
 	if (state->gui != NULL) {
 		g_object_unref (G_OBJECT (state->gui));
 		state->gui = NULL;
@@ -359,7 +478,6 @@ cb_dialog_solver_destroy (SolverState *state)
 	wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
 
 	state->dialog = NULL;
-	g_free (state);
 }
 
 static void
@@ -621,7 +739,9 @@ run_solver (SolverState *state, GnmSolverParameters *param)
 
 		gnm_solver_store_result (sol);
 		redo = clipboard_copy_range_undo (sr.sheet, &sr.range);
-		cmd_solver (WORKBOOK_CONTROL (state->wbcg), undo, redo);
+		cmd_solver (WORKBOOK_CONTROL (state->wbcg),
+			    _("Running solver"),
+			    undo, redo);
 		res = g_object_ref (sol->result);
 		undo = redo = NULL;
 		break;
@@ -670,90 +790,16 @@ static void
 cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 			 SolverState *state)
 {
-	GnmSolverResult           *res;
-	GnmValue                   *target_range;
-	GnmValue                   *input_range;
-	gboolean                answer, sensitivity, limits, performance;
-	gboolean                program, dual_program;
+	GnmSolverResult *res;
+	GnmSolverParameters *param = state->sheet->solver_parameters;
 	GError *err = NULL;
-	GnmSolverParameters        *param;
-	GtkTreeIter iter;
-	GnmCell *target_cell;
-	GnmSolverFactory *factory = NULL;
-
-	param = state->sheet->solver_parameters;
 
-	if (state->warning_dialog != NULL)
+	if (state->warning_dialog != NULL) {
 		gtk_widget_destroy (state->warning_dialog);
+		state->warning_dialog = NULL;
+	}
 
-	target_range = gnm_expr_entry_parse_as_value (state->target_entry,
-						      state->sheet);
-	input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
-						     state->sheet);
-
-	gnm_solver_param_set_input (param, input_range);
-
-	gnm_solver_param_set_target (param,
-				     target_range
-				     ? &target_range->v_range.cell.a
-				     : NULL);
-	target_cell = gnm_solver_param_get_target_cell (param);
-
-	param->problem_type =
-		gnumeric_glade_group_value (state->gui, problem_type_group);
-	param->options.model_type =
-		gnumeric_glade_group_value (state->gui, model_type_group);
-
-	gtk_combo_box_get_active_iter (state->algorithm_combo, &iter);
-	gtk_tree_model_get (gtk_combo_box_get_model (state->algorithm_combo),
-			    &iter, 1, &factory, -1);
-	param->options.algorithm = factory;
-
-	param->options.assume_non_negative = gtk_toggle_button_get_active
-		(GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui,
-							  "non_neg_button")));
-	param->options.assume_discrete = gtk_toggle_button_get_active
-		(GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui,
-							  "all_int_button")));
-	param->options.automatic_scaling = gtk_toggle_button_get_active
-		(GTK_TOGGLE_BUTTON (glade_xml_get_widget
-				    (state->gui, "autoscale_button")));
-
-	param->options.max_iter = gtk_spin_button_get_value
-		(GTK_SPIN_BUTTON (state->max_iter_entry));
-	param->options.max_time_sec = gtk_spin_button_get_value
-		(GTK_SPIN_BUTTON (state->max_time_entry));
-
-	answer = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget (state->gui, "answer")));
-	param->options.answer_report = answer;
-
-	sensitivity = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget (state->gui, "sensitivity")));
-	param->options.sensitivity_report = sensitivity;
-
-	limits = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget (state->gui, "limits")));
-	param->options.limits_report = limits;
-
-	performance = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget (state->gui, "performance")));
-	param->options.performance_report = performance;
-
-	program = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget (state->gui, "program")));
-	param->options.program_report = program;
-
-	g_free (param->options.scenario_name);
-	param->options.scenario_name = g_strdup
-		(gtk_entry_get_text (GTK_ENTRY (state->scenario_name_entry)));
-
-	param->options.add_scenario = gtk_toggle_button_get_active
-		(GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui,
-							  "optimal_scenario")));
-
-	dual_program = FALSE;
-	param->options.dual_program_report = dual_program;
+	extract_settings (state);
 
 	if (!gnm_solver_param_valid (param, &err)) {
 		GtkWidget *top = gtk_widget_get_toplevel (state->dialog);
@@ -762,6 +808,8 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 		goto out;
 	}
 
+	check_for_changed_options (state);
+
 	res = run_solver (state, param);
 
 	workbook_recalc (state->sheet->workbook);
@@ -786,11 +834,28 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 	}
 
  out:
-	value_release (target_range);
 	if (err)
 		g_error_free (err);
 }
 
+static void
+bool_entry_changed (GtkToggleButton *tb, SolverState *state)
+{
+	GnmSolverParameters *param = state->sheet->solver_parameters;
+	gulong offset =
+		GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (tb), "offset"));
+	gboolean *pb =
+		(gboolean *)((char *)param + offset);
+	*pb = gtk_toggle_button_get_active (tb);
+}
+
+#define INIT_BOOL_ENTRY(name_, field_)					\
+do {									\
+	GtkWidget *w_ = glade_xml_get_widget (state->gui, (name_));	\
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w_),		\
+				      param->field_);			\
+} while (0)
+
 
 /**
  * dialog_init:
@@ -802,8 +867,8 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
 static gboolean
 dialog_init (SolverState *state)
 {
-	GtkTable                *table;
-	GnmSolverParameters        *param;
+	GtkTable *table;
+	GnmSolverParameters *param;
 	GtkCellRenderer *renderer;
 	GtkListStore *store;
 	GtkTreeViewColumn *column;
@@ -904,15 +969,19 @@ dialog_init (SolverState *state)
 	/* Options */
 	state->max_iter_entry = glade_xml_get_widget (state->gui,
 						      "max_iter_entry");
-	if (state->max_iter_entry == NULL)
-		return TRUE;
-	gtk_entry_set_text (GTK_ENTRY (state->max_iter_entry), "200");
+	{
+		char *txt = g_strdup_printf ("%d", param->options.max_iter);
+		gtk_entry_set_text (GTK_ENTRY (state->max_iter_entry), txt);
+		g_free (txt);
+	}
 
 	state->max_time_entry = glade_xml_get_widget (state->gui,
 						      "max_time_entry");
-	if (state->max_time_entry == NULL)
-		return TRUE;
-	gtk_entry_set_text (GTK_ENTRY (state->max_time_entry), "30");
+	{
+		char *txt = g_strdup_printf ("%d", param->options.max_time_sec);
+		gtk_entry_set_text (GTK_ENTRY (state->max_time_entry), txt);
+		g_free (txt);
+	}
 
 /* lhs_entry */
 	table = GTK_TABLE (glade_xml_get_widget (state->gui, "edit-table"));
@@ -1014,28 +1083,13 @@ dialog_init (SolverState *state)
 		g_free (str);
 	}
 
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "non_neg_button")),
-			param->options.assume_non_negative);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "all_int_button")),
-			param->options.assume_discrete);
-
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "answer")),
-			param->options.answer_report);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "sensitivity")),
-			param->options.sensitivity_report);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "limits")),
-			param->options.limits_report);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "performance")),
-			param->options.performance_report);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
-		glade_xml_get_widget(state->gui, "program")),
-			param->options.program_report);
+	INIT_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
+	INIT_BOOL_ENTRY ("all_int_button", options.assume_discrete);
+	INIT_BOOL_ENTRY ("answer", options.answer_report);
+	INIT_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
+	INIT_BOOL_ENTRY ("limits", options.limits_report);
+	INIT_BOOL_ENTRY ("performance", options.performance_report);
+	INIT_BOOL_ENTRY ("program", options.program_report);
 
 	input = gnm_solver_param_get_input (param);
 	if (input != NULL)
@@ -1045,6 +1099,7 @@ dialog_init (SolverState *state)
 	if (target_cell)
 		gnm_expr_entry_load_from_text (state->target_entry,
 					       cell_name (target_cell));
+
 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
 		glade_xml_get_widget(state->gui, "max_button")),
 			param->problem_type == GNM_SOLVER_MAXIMIZE);
@@ -1066,8 +1121,6 @@ dialog_init (SolverState *state)
 
 	state->scenario_name_entry = glade_xml_get_widget
 		(state->gui, "scenario_name_entry");
-	if (state->scenario_name_entry == NULL)
-		return TRUE;
 	gtk_entry_set_text (GTK_ENTRY (state->scenario_name_entry),
 			    param->options.scenario_name);
 
@@ -1079,8 +1132,14 @@ dialog_init (SolverState *state)
 
 /* dialog */
 	wbc_gtk_attach_guru (state->wbcg, state->dialog);
+
+	g_signal_connect_swapped (G_OBJECT (state->dialog),
+				  "destroy",
+				  G_CALLBACK (cb_dialog_solver_destroy),
+				  state);
 	g_object_set_data_full (G_OBJECT (state->dialog),
-		"state", state, (GDestroyNotify) cb_dialog_solver_destroy);
+				"state", state,
+				(GDestroyNotify)free_state);
 
 	return FALSE;
 }
@@ -1106,11 +1165,13 @@ dialog_solver (WBCGtk *wbcg, Sheet *sheet)
 	state->wbcg           = wbcg;
 	state->sheet          = sheet;
 	state->warning_dialog = NULL;
+	state->orig_params = gnm_solver_param_dup (sheet->solver_parameters,
+						   sheet);
 
 	if (dialog_init (state)) {
 		go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
 				 _("Could not create the Solver dialog."));
-		g_free (state);
+		free_state (state);
 		return;
 	}
 
diff --git a/src/expr.c b/src/expr.c
index 7541795..c9d8bb4 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -2889,6 +2889,8 @@ gnm_expr_top_equal (GnmExprTop const *te1, GnmExprTop const *te2)
 {
 	if (te1 == te2)
 		return TRUE;
+	if (te1 == NULL || te2 == NULL)
+		return FALSE;
 
 	g_return_val_if_fail (IS_GNM_EXPR_TOP (te1), FALSE);
 	g_return_val_if_fail (IS_GNM_EXPR_TOP (te2), FALSE);
diff --git a/src/sheet.c b/src/sheet.c
index d13dd6e..6b584ba 100644
--- a/src/sheet.c
+++ b/src/sheet.c
@@ -5768,6 +5768,16 @@ gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
 		: workbook_get_sheet_size (wb);
 }
 
+void
+gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
+{
+	g_return_if_fail (IS_SHEET (sheet));
+	g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
+
+	g_object_ref (param);
+	g_object_unref (sheet->solver_parameters);
+	sheet->solver_parameters = param;
+}
 
 GHashTable *
 gnm_sheet_get_sort_setups (Sheet *sheet)
diff --git a/src/sheet.h b/src/sheet.h
index 90814d0..a0a098f 100644
--- a/src/sheet.h
+++ b/src/sheet.h
@@ -279,6 +279,8 @@ gboolean sheet_range_has_heading     (Sheet const *sheet, GnmRange const *src,
 
 void gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data);
 
+void gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param);
+
 GHashTable *gnm_sheet_get_sort_setups (Sheet *sheet);
 void gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup);
 gconstpointer gnm_sheet_find_sort_setup (Sheet *sheet, char const *key);
diff --git a/src/tools/gnm-solver.c b/src/tools/gnm-solver.c
index 131c018..9048a3c 100644
--- a/src/tools/gnm-solver.c
+++ b/src/tools/gnm-solver.c
@@ -120,12 +120,22 @@ gnm_solver_constraint_dup (GnmSolverConstraint *c, Sheet *sheet)
 {
 	GnmSolverConstraint *res = gnm_solver_constraint_new (sheet);
 	res->type = c->type;
-	dependent_managed_set_expr (&res->lhs, res->lhs.texpr);
-	dependent_managed_set_expr (&res->rhs, res->lhs.texpr);
+	dependent_managed_set_expr (&res->lhs, c->lhs.texpr);
+	dependent_managed_set_expr (&res->rhs, c->rhs.texpr);
 	return res;
 }
 
 gboolean
+gnm_solver_constraint_equal (GnmSolverConstraint const *a,
+			     GnmSolverConstraint const *b)
+{
+	return (a->type == b->type &&
+		gnm_expr_top_equal (a->lhs.texpr, b->lhs.texpr) &&
+		(!gnm_solver_constraint_has_rhs (a) ||
+		 gnm_expr_top_equal (a->rhs.texpr, b->rhs.texpr)));
+}
+
+gboolean
 gnm_solver_constraint_has_rhs (GnmSolverConstraint const *c)
 {
 	g_return_val_if_fail (c != NULL, FALSE);
@@ -403,6 +413,45 @@ gnm_solver_param_dup (GnmSolverParameters *src, Sheet *new_sheet)
 	return dst;
 }
 
+gboolean
+gnm_solver_param_equal (GnmSolverParameters const *a,
+			GnmSolverParameters const *b)
+{
+	GSList *la, *lb;
+
+	if (a->sheet != b->sheet ||
+	    a->problem_type != b->problem_type ||
+	    !gnm_expr_top_equal (a->target.texpr, b->target.texpr) ||
+	    !gnm_expr_top_equal (a->input.texpr, b->input.texpr) ||
+	    a->options.max_time_sec != b->options.max_time_sec ||
+	    a->options.max_iter != b->options.max_iter ||
+	    a->options.algorithm != b->options.algorithm ||
+	    a->options.model_type != b->options.model_type ||
+            a->options.assume_non_negative != b->options.assume_non_negative ||
+            a->options.assume_discrete != b->options.assume_discrete ||
+            a->options.automatic_scaling != b->options.automatic_scaling ||
+            a->options.show_iter_results != b->options.show_iter_results ||
+            a->options.answer_report != b->options.answer_report ||
+            a->options.sensitivity_report != b->options.sensitivity_report ||
+            a->options.limits_report != b->options.limits_report ||
+            a->options.performance_report != b->options.performance_report ||
+            a->options.program_report != b->options.program_report ||
+            a->options.dual_program_report != b->options.dual_program_report ||
+            a->options.add_scenario != b->options.add_scenario ||
+	    strcmp (a->options.scenario_name, b->options.scenario_name))
+		return FALSE;
+
+	for (la = a->constraints, lb = b->constraints;
+	     la && lb;
+	     la = la->next, lb = lb->next) {
+		GnmSolverConstraint *ca = la->data;
+		GnmSolverConstraint *cb = lb->data;
+		if (!gnm_solver_constraint_equal (ca, cb))
+			return FALSE;
+	}
+	return la == lb;
+}
+
 GnmValue const *
 gnm_solver_param_get_input (GnmSolverParameters const *sp)
 {
@@ -453,17 +502,19 @@ gnm_solver_param_get_input_cells (GnmSolverParameters const *sp)
 void
 gnm_solver_param_set_target (GnmSolverParameters *sp, GnmCellRef const *cr)
 {
-	GnmCellRef cr2 = *cr;
-	GnmExprTop const *texpr;
-
-	/* Make reference absolute to avoid tracking problems on row/col
-	   insert.  */
-	cr2.row_relative = FALSE;
-	cr2.col_relative = FALSE;
-
-	texpr = gnm_expr_top_new (gnm_expr_new_cellref (&cr2));
-	dependent_managed_set_expr (&sp->target, texpr);
-	gnm_expr_top_unref (texpr);
+	if (cr) {
+		GnmExprTop const *texpr;
+		GnmCellRef cr2 = *cr;
+		/* Make reference absolute to avoid tracking problems on row/col
+		   insert.  */
+		cr2.row_relative = FALSE;
+		cr2.col_relative = FALSE;
+
+		texpr = gnm_expr_top_new (gnm_expr_new_cellref (&cr2));
+		dependent_managed_set_expr (&sp->target, texpr);
+		gnm_expr_top_unref (texpr);
+	} else
+		dependent_managed_set_expr (&sp->target, NULL);
 }
 
 const GnmCellRef *
@@ -565,9 +616,12 @@ gnm_solver_param_constructor (GType type,
 	dependent_managed_init (&sp->target, sp->sheet);
 	dependent_managed_init (&sp->input, sp->sheet);
 
-	sp->options.model_type          = GNM_SOLVER_LP;
+	sp->options.model_type = GNM_SOLVER_LP;
+	sp->options.max_iter = 100;
+	sp->options.max_time_sec = 30;
 	sp->options.assume_non_negative = TRUE;
 	sp->options.scenario_name = g_strdup ("Optimal");
+	sp->options.algorithm = g_slist_nth_data (gnm_solver_db_get (), 0);
 
 	return obj;
 }
@@ -1128,8 +1182,16 @@ gnm_sub_solver_spawn (GnmSubSolver *subsol,
 	if (io_stdout == NULL)
 		spflags |= G_SPAWN_STDOUT_TO_DEV_NULL;
 
-	if (debug_solver ())
-		g_printerr ("Spawning %s\n", argv[0]);
+	if (debug_solver ()) {
+		GString *msg = g_string_new ("Spawning");
+		int i;
+		for (i = 0; argv[i]; i++) {
+			g_string_append_c (msg, ' ');
+			g_string_append (msg, argv[i]);
+		}
+		g_printerr ("%s\n", msg->str);
+		g_string_free (msg, TRUE);
+	}
 
 	ok = g_spawn_async_with_pipes
 		(g_get_home_dir (),  /* PWD */
@@ -1306,13 +1368,20 @@ gnm_solver_factory_create (GnmSolverFactory *factory,
 	return factory->creator (factory, param);
 }
 
+static int
+cb_compare_factories (GnmSolverFactory *a, GnmSolverFactory *b)
+{
+	return go_utf8_collate_casefold (a->name, b->name);
+}
+
 void
 gnm_solver_db_register (GnmSolverFactory *factory)
 {
 	if (debug_solver ())
 		g_printerr ("Registering %s\n", factory->id);
 	g_object_ref (factory);
-	solvers = g_slist_prepend (solvers, factory);
+	solvers = g_slist_insert_sorted (solvers, factory,
+					 (GCompareFunc)cb_compare_factories);
 }
 
 void
diff --git a/src/tools/gnm-solver.h b/src/tools/gnm-solver.h
index d93da60..f336d2c 100644
--- a/src/tools/gnm-solver.h
+++ b/src/tools/gnm-solver.h
@@ -70,6 +70,8 @@ GnmSolverConstraint *gnm_solver_constraint_new (Sheet *sheet);
 void gnm_solver_constraint_free (GnmSolverConstraint *c);
 GnmSolverConstraint *gnm_solver_constraint_dup (GnmSolverConstraint *c,
 						Sheet *sheet);
+gboolean gnm_solver_constraint_equal (GnmSolverConstraint const *a,
+				      GnmSolverConstraint const *b);
 
 void gnm_solver_constraint_set_old (GnmSolverConstraint *c,
 				    GnmSolverConstraintType type,
@@ -119,6 +121,7 @@ typedef struct {
 
 #define GNM_SOLVER_PARAMETERS_TYPE   (gnm_solver_param_get_type ())
 #define GNM_SOLVER_PARAMETERS(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SOLVER_PARAMETERS_TYPE, GnmSolverParameters))
+#define GNM_IS_SOLVER_PARAMETERS(o)  (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_SOLVER_PARAMETERS_TYPE))
 
 struct GnmSolverParameters_ {
 	GObject parent;
@@ -153,6 +156,9 @@ GnmSolverParameters *gnm_solver_param_new (Sheet *sheet);
 GnmSolverParameters *gnm_solver_param_dup (GnmSolverParameters *src_param,
 					   Sheet *new_sheet);
 
+gboolean gnm_solver_param_equal (GnmSolverParameters const *a,
+				 GnmSolverParameters const *b);
+				 
 GnmValue const *gnm_solver_param_get_input (GnmSolverParameters const *sp);
 void gnm_solver_param_set_input (GnmSolverParameters *sp, GnmValue *v);
 GSList *gnm_solver_param_get_input_cells (GnmSolverParameters const *sp);



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