Update Spanish translation
[gnumeric.git] / src / dialogs / dialog-solver.c
blobb4d109af386287ebbbe680f7bdf204c8b592d704
1 /*
2 * dialog-solver.c:
4 * Author:
5 * Jukka-Pekka Iivonen <iivonen@iki.fi>
7 * (C) Copyright 2000, 2002 by Jukka-Pekka Iivonen <iivonen@iki.fi>
8 * (C) Copyright 2009-2013 Morten Welinder (terra@gnome.org)
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <https://www.gnu.org/licenses/>.
24 #include <gnumeric-config.h>
25 #include <glib/gi18n-lib.h>
26 #include <gnumeric.h>
27 #include <dialogs/dialogs.h>
28 #include <dialogs/help.h>
30 #include <gui-util.h>
31 #include <func.h>
32 #include <dialogs/tool-dialogs.h>
33 #include <value.h>
34 #include <cell.h>
35 #include <sheet.h>
36 #include <sheet-view.h>
37 #include <expr.h>
38 #include <wbc-gtk.h>
39 #include <workbook.h>
40 #include <parse-util.h>
41 #include <ranges.h>
42 #include <commands.h>
43 #include <clipboard.h>
44 #include <selection.h>
45 #include <application.h>
46 #include <tools/gnm-solver.h>
47 #include <widgets/gnm-expr-entry.h>
49 #include <goffice/goffice.h>
50 #include <string.h>
51 #include <tools/scenarios.h>
53 #define SOLVER_KEY "solver-dialog"
55 typedef struct {
56 int ref_count;
57 GtkBuilder *gui;
58 GtkWidget *dialog;
59 GtkWidget *notebook;
60 GnmExprEntry *target_entry;
61 GnmExprEntry *change_cell_entry;
62 GtkWidget *max_iter_entry;
63 GtkWidget *max_time_entry;
64 GtkWidget *gradient_order_entry;
65 GtkWidget *solve_button;
66 GtkWidget *close_button;
67 GtkWidget *stop_button;
68 GtkWidget *help_button;
69 GtkWidget *add_button;
70 GtkWidget *change_button;
71 GtkWidget *delete_button;
72 GtkWidget *scenario_name_entry;
73 struct {
74 GnmExprEntry*entry;
75 GtkWidget *label;
76 } lhs, rhs;
77 GtkComboBox *type_combo;
78 GtkComboBox *algorithm_combo;
79 GtkTreeView *constraint_list;
80 GnmSolverConstraint *constr;
81 GtkWidget *warning_dialog;
83 struct {
84 GnmSolver *solver;
85 GtkWidget *timer_widget;
86 guint timer_source;
87 GtkWidget *status_widget;
88 GtkWidget *problem_status_widget;
89 GtkWidget *objective_value_widget;
90 guint obj_val_source;
91 GtkWidget *spinner;
92 guint in_main;
93 } run;
95 Sheet *sheet;
96 WBCGtk *wbcg;
98 GnmSolverParameters *orig_params;
99 } SolverState;
102 static char const * const problem_type_group[] = {
103 "min_button",
104 "max_button",
105 "equal_to_button",
106 NULL
109 static char const * const model_type_group[] = {
110 "lp_model_button",
111 "qp_model_button",
112 "nlp_model_button",
113 NULL
116 static void
117 constraint_fill (GnmSolverConstraint *c, SolverState *state)
119 Sheet *sheet = state->sheet;
121 c->type = gtk_combo_box_get_active (state->type_combo);
123 gnm_solver_constraint_set_lhs
125 gnm_expr_entry_parse_as_value (state->lhs.entry, sheet));
127 gnm_solver_constraint_set_rhs
129 gnm_solver_constraint_has_rhs (c)
130 ? gnm_expr_entry_parse_as_value (state->rhs.entry, sheet)
131 : NULL);
134 static gboolean
135 dialog_set_sec_button_sensitivity (G_GNUC_UNUSED GtkWidget *dummy,
136 SolverState *state)
138 gboolean select_ready = (state->constr != NULL);
139 GnmSolverConstraint *test = gnm_solver_constraint_new (NULL);
140 gboolean ready, has_rhs;
141 GnmSolverParameters const *param = state->sheet->solver_parameters;
143 constraint_fill (test, state);
144 ready = gnm_solver_constraint_valid (test, param);
145 has_rhs = gnm_solver_constraint_has_rhs (test);
146 gnm_solver_constraint_free (test);
148 gtk_widget_set_sensitive (state->add_button, ready);
149 gtk_widget_set_sensitive (state->change_button, select_ready && ready);
150 gtk_widget_set_sensitive (state->delete_button, select_ready);
151 gtk_widget_set_sensitive (GTK_WIDGET (state->rhs.entry), has_rhs);
152 gtk_widget_set_sensitive (GTK_WIDGET (state->rhs.label), has_rhs);
154 /* Return %TRUE iff the current constraint is valid. */
155 return ready;
158 static void
159 constraint_select_click (GtkTreeSelection *Selection,
160 SolverState * state)
162 GtkTreeModel *store;
163 GtkTreeIter iter;
164 GnmSolverConstraint const *c;
165 GnmValue const *lhs, *rhs;
167 if (gtk_tree_selection_get_selected (Selection, &store, &iter))
168 gtk_tree_model_get (store, &iter, 1, &state->constr, -1);
169 else
170 state->constr = NULL;
171 dialog_set_sec_button_sensitivity (NULL, state);
173 if (state->constr == NULL)
174 return; /* Fail? */
175 c = state->constr;
177 lhs = gnm_solver_constraint_get_lhs (c);
178 if (lhs) {
179 GnmExprTop const *texpr =
180 gnm_expr_top_new_constant (value_dup (lhs));
181 GnmParsePos pp;
183 gnm_expr_entry_load_from_expr
184 (state->lhs.entry,
185 texpr,
186 parse_pos_init_sheet (&pp, state->sheet));
187 gnm_expr_top_unref (texpr);
188 } else
189 gnm_expr_entry_load_from_text (state->lhs.entry, "");
191 rhs = gnm_solver_constraint_get_rhs (c);
192 if (rhs && gnm_solver_constraint_has_rhs (c)) {
193 GnmExprTop const *texpr =
194 gnm_expr_top_new_constant (value_dup (rhs));
195 GnmParsePos pp;
197 gnm_expr_entry_load_from_expr
198 (state->rhs.entry,
199 texpr,
200 parse_pos_init_sheet (&pp, state->sheet));
201 gnm_expr_top_unref (texpr);
202 } else
203 gnm_expr_entry_load_from_text (state->rhs.entry, "");
205 gtk_combo_box_set_active (state->type_combo, c->type);
209 * cb_dialog_delete_clicked:
210 * @button:
211 * @state:
215 static void
216 cb_dialog_delete_clicked (G_GNUC_UNUSED GtkWidget *button, SolverState *state)
218 if (state->constr != NULL) {
219 GtkTreeIter iter;
220 GtkTreeModel *store;
221 GnmSolverParameters *param = state->sheet->solver_parameters;
223 param->constraints =
224 g_slist_remove (param->constraints, state->constr);
225 gnm_solver_constraint_free (state->constr);
226 state->constr = NULL;
228 if (gtk_tree_selection_get_selected (
229 gtk_tree_view_get_selection (state->constraint_list),
230 &store, &iter))
231 gtk_list_store_remove ((GtkListStore*)store, &iter);
235 static void
236 constraint_fill_row (SolverState *state, GtkListStore *store, GtkTreeIter *iter)
238 char *text;
239 GnmSolverConstraint *c = state->constr;
241 constraint_fill (c, state);
243 text = gnm_solver_constraint_as_str (c, state->sheet);
244 gtk_list_store_set (store, iter, 0, text, 1, c, -1);
245 g_free (text);
246 gtk_tree_selection_select_iter (gtk_tree_view_get_selection (state->constraint_list), iter);
249 static void
250 cb_dialog_add_clicked (SolverState *state)
252 if (dialog_set_sec_button_sensitivity (NULL, state)) {
253 GtkTreeIter iter;
254 GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (state->constraint_list));
255 GnmSolverParameters *param = state->sheet->solver_parameters;
257 gtk_list_store_append (store, &iter);
258 state->constr = gnm_solver_constraint_new (state->sheet);
259 constraint_fill_row (state, store, &iter);
260 param->constraints =
261 g_slist_append (param->constraints, state->constr);
265 static void
266 cb_dialog_change_clicked (GtkWidget *button, SolverState *state)
268 if (state->constr != NULL) {
269 GtkTreeIter iter;
270 GtkTreeModel *store;
272 if (gtk_tree_selection_get_selected (
273 gtk_tree_view_get_selection (state->constraint_list),
274 &store, &iter))
275 constraint_fill_row (state, (GtkListStore *)store, &iter);
279 static void
280 dialog_set_main_button_sensitivity (G_GNUC_UNUSED GtkWidget *dummy,
281 SolverState *state)
283 gboolean ready;
285 ready = gnm_expr_entry_is_cell_ref (state->target_entry, state->sheet,
286 FALSE)
287 && gnm_expr_entry_is_cell_ref (state->change_cell_entry,
288 state->sheet, TRUE);
289 gtk_widget_set_sensitive (state->solve_button, ready);
292 static gboolean
293 fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
295 GtkListStore *store =
296 gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
297 GSList *solvers, *l;
298 int sel = 0, i;
299 GnmSolverParameters *param =state->sheet->solver_parameters;
301 gtk_combo_box_set_model (state->algorithm_combo, GTK_TREE_MODEL (store));
303 l = NULL;
304 for (solvers = gnm_solver_db_get (); solvers; solvers = solvers->next) {
305 GnmSolverFactory *entry = solvers->data;
306 if (type != entry->type)
307 continue;
308 l = g_slist_prepend (l, entry);
310 solvers = g_slist_reverse (l);
312 gtk_widget_set_sensitive (GTK_WIDGET (state->solve_button),
313 solvers != NULL);
314 if (!solvers)
315 return FALSE;
317 for (l = solvers, i = 0; l; l = l->next, i++) {
318 GnmSolverFactory *factory = l->data;
319 GtkTreeIter iter;
321 if (param->options.algorithm == factory)
322 sel = i;
324 gtk_list_store_append (store, &iter);
325 gtk_list_store_set (store, &iter,
326 0, factory->name,
327 1, factory,
328 -1);
330 g_slist_free (solvers);
332 gtk_combo_box_set_active (state->algorithm_combo, sel);
334 g_object_unref (store);
336 return TRUE;
339 static void
340 cb_dialog_model_type_clicked (G_GNUC_UNUSED GtkWidget *button,
341 SolverState *state)
343 GnmSolverModelType type;
344 gboolean any;
346 type = gnm_gui_group_value (state->gui, model_type_group);
347 any = fill_algorithm_combo (state, type);
349 if (!any) {
350 go_gtk_notice_nonmodal_dialog
351 (GTK_WINDOW (state->dialog),
352 &(state->warning_dialog),
353 GTK_MESSAGE_INFO,
354 _("Looking for a subject for your thesis? "
355 "Maybe you would like to write a solver for "
356 "Gnumeric?"));
360 static void
361 unref_state (SolverState *state)
363 state->ref_count--;
364 if (state->ref_count > 0)
365 return;
367 if (state->orig_params)
368 g_object_unref (state->orig_params);
369 g_free (state);
372 static GOUndo *
373 set_params (Sheet *sheet, GnmSolverParameters *params)
375 return go_undo_binary_new
376 (sheet, g_object_ref (params),
377 (GOUndoBinaryFunc)gnm_sheet_set_solver_params,
378 NULL, g_object_unref);
381 #define GET_BOOL_ENTRY(name_, field_) \
382 do { \
383 GtkWidget *w_ = go_gtk_builder_get_widget (state->gui, (name_)); \
384 param->field_ = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w_)); \
385 } while (0)
387 static void
388 extract_settings (SolverState *state)
390 GnmSolverParameters *param = state->sheet->solver_parameters;
391 GtkTreeIter iter;
392 GnmValue *target_range;
393 GnmValue *input_range;
394 GnmSolverFactory *factory = NULL;
396 target_range = gnm_expr_entry_parse_as_value (state->target_entry,
397 state->sheet);
398 input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
399 state->sheet);
401 gnm_solver_param_set_input (param, input_range);
403 gnm_solver_param_set_target (param,
404 target_range
405 ? &target_range->v_range.cell.a
406 : NULL);
408 param->problem_type =
409 gnm_gui_group_value (state->gui, problem_type_group);
410 param->options.model_type =
411 gnm_gui_group_value (state->gui, model_type_group);
413 if (gtk_combo_box_get_active_iter (state->algorithm_combo, &iter)) {
414 gtk_tree_model_get (gtk_combo_box_get_model (state->algorithm_combo),
415 &iter, 1, &factory, -1);
416 gnm_solver_param_set_algorithm (param, factory);
417 } else
418 gnm_solver_param_set_algorithm (param, NULL);
420 param->options.max_iter = gtk_spin_button_get_value
421 (GTK_SPIN_BUTTON (state->max_iter_entry));
422 param->options.max_time_sec = gtk_spin_button_get_value
423 (GTK_SPIN_BUTTON (state->max_time_entry));
424 param->options.gradient_order = gtk_spin_button_get_value
425 (GTK_SPIN_BUTTON (state->gradient_order_entry));
427 GET_BOOL_ENTRY ("autoscale_button", options.automatic_scaling);
428 GET_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
429 GET_BOOL_ENTRY ("all_int_button", options.assume_discrete);
430 GET_BOOL_ENTRY ("program", options.program_report);
431 GET_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
433 g_free (param->options.scenario_name);
434 param->options.scenario_name = g_strdup
435 (gtk_entry_get_text (GTK_ENTRY (state->scenario_name_entry)));
437 GET_BOOL_ENTRY ("optimal_scenario", options.add_scenario);
439 value_release (target_range);
442 #undef GET_BOOL_ENTRY
444 static void
445 check_for_changed_options (SolverState *state)
447 Sheet *sheet = state->sheet;
449 if (!gnm_solver_param_equal (sheet->solver_parameters,
450 state->orig_params)) {
451 GOUndo *undo = set_params (sheet, state->orig_params);
452 GOUndo *redo = set_params (sheet, sheet->solver_parameters);
453 cmd_generic (GNM_WBC (state->wbcg),
454 _("Changing solver parameters"),
455 undo, redo);
457 g_object_unref (state->orig_params);
458 state->orig_params =
459 gnm_solver_param_dup (sheet->solver_parameters,
460 sheet);
464 static void
465 cb_dialog_solver_destroy (SolverState *state)
467 g_return_if_fail (state != NULL);
469 if (state->run.solver) {
470 gnm_solver_stop (state->run.solver, NULL);
471 g_object_set (state->run.solver, "result", NULL, NULL);
474 extract_settings (state);
476 check_for_changed_options (state);
478 if (state->gui != NULL) {
479 g_object_unref (state->gui);
480 state->gui = NULL;
483 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
485 state->dialog = NULL;
488 static void
489 cb_dialog_close_clicked (G_GNUC_UNUSED GtkWidget *button,
490 SolverState *state)
492 gtk_widget_destroy (state->dialog);
495 static void
496 cb_stop_solver (SolverState *state)
498 GnmSolver *sol = state->run.solver;
500 switch (sol->status) {
501 case GNM_SOLVER_STATUS_RUNNING: {
502 gboolean ok = gnm_solver_stop (sol, NULL);
503 if (!ok) {
504 g_warning ("Failed to stop solver!");
506 g_object_set (sol, "result", NULL, NULL);
507 break;
510 default:
511 break;
515 static void
516 remove_timer_source (SolverState *state)
518 if (state->run.timer_source) {
519 g_source_remove (state->run.timer_source);
520 state->run.timer_source = 0;
524 static void
525 remove_objective_value_source (SolverState *state)
527 if (state->run.obj_val_source) {
528 g_source_remove (state->run.obj_val_source);
529 state->run.obj_val_source = 0;
533 static void
534 update_obj_value (SolverState *state)
536 GnmSolver *sol = state->run.solver;
537 GnmSolverResult *r = sol->result;
538 char *valtxt;
539 const char *txt;
541 switch (r ? r->quality : GNM_SOLVER_RESULT_NONE) {
542 default:
543 case GNM_SOLVER_RESULT_NONE:
544 txt = "";
545 break;
547 case GNM_SOLVER_RESULT_FEASIBLE:
548 txt = _("Feasible");
549 break;
551 case GNM_SOLVER_RESULT_OPTIMAL:
552 txt = _("Optimal");
553 break;
555 case GNM_SOLVER_RESULT_INFEASIBLE:
556 txt = _("Infeasible");
557 break;
559 case GNM_SOLVER_RESULT_UNBOUNDED:
560 txt = _("Unbounded");
561 break;
563 gtk_label_set_text (GTK_LABEL (state->run.problem_status_widget), txt);
565 if (gnm_solver_has_solution (sol)) {
566 txt = valtxt = gnm_format_value (go_format_general (),
567 r->value);
568 } else {
569 valtxt = NULL;
570 txt = "";
573 gtk_label_set_text (GTK_LABEL (state->run.objective_value_widget),
574 txt);
575 g_free (valtxt);
577 remove_objective_value_source (state);
580 static void
581 cb_notify_status (SolverState *state)
583 GnmSolver *sol = state->run.solver;
584 const char *text;
585 gboolean finished = gnm_solver_finished (sol);
586 gboolean running = FALSE;
588 switch (sol->status) {
589 case GNM_SOLVER_STATUS_READY:
590 text = _("Ready");
591 break;
592 case GNM_SOLVER_STATUS_PREPARING:
593 text = _("Preparing");
594 break;
595 case GNM_SOLVER_STATUS_PREPARED:
596 text = _("Prepared");
597 break;
598 case GNM_SOLVER_STATUS_RUNNING:
599 text = _("Running");
600 running = TRUE;
601 break;
602 case GNM_SOLVER_STATUS_DONE:
603 text = _("Done");
604 break;
605 default:
606 case GNM_SOLVER_STATUS_ERROR:
607 text = _("Error");
608 break;
609 case GNM_SOLVER_STATUS_CANCELLED:
610 text = _("Cancelled");
611 break;
614 if (sol->reason) {
615 char *text2 = g_strconcat (text,
616 " (", sol->reason, ")",
617 NULL);
618 gtk_label_set_text (GTK_LABEL (state->run.status_widget),
619 text2);
620 g_free (text2);
621 } else {
622 gtk_label_set_text (GTK_LABEL (state->run.status_widget), text);
625 gtk_widget_set_visible (state->run.spinner, running);
626 gtk_widget_set_visible (state->stop_button, !finished);
627 gtk_widget_set_sensitive (state->solve_button, finished);
628 gtk_widget_set_sensitive (state->close_button, finished);
630 if (state->run.obj_val_source)
631 update_obj_value (state);
633 if (finished) {
634 remove_timer_source (state);
635 if (state->run.in_main)
636 gtk_main_quit ();
640 static gboolean
641 cb_obj_val_tick (SolverState *state)
643 state->run.obj_val_source = 0;
644 update_obj_value (state);
645 return FALSE;
648 static void
649 cb_notify_result (SolverState *state)
651 if (state->run.obj_val_source == 0)
652 state->run.obj_val_source = g_timeout_add
653 (100, (GSourceFunc)cb_obj_val_tick, state);
656 static gboolean
657 cb_timer_tick (SolverState *state)
659 GnmSolver *sol = state->run.solver;
660 double dsecs = gnm_solver_elapsed (sol);
661 int secs = (int)CLAMP (dsecs, 0, INT_MAX);
662 int hh = secs / 3600;
663 int mm = secs / 60 % 60;
664 int ss = secs % 60;
665 char *txt = hh
666 ? g_strdup_printf ("%02d:%02d:%02d", hh, mm, ss)
667 : g_strdup_printf ("%02d:%02d", mm, ss);
669 gtk_label_set_text (GTK_LABEL (state->run.timer_widget), txt);
670 g_free (txt);
672 if (gnm_solver_check_timeout (sol)) {
673 cb_notify_status (state);
676 return TRUE;
679 static void
680 create_report (GnmSolver *sol, SolverState *state)
682 Sheet *sheet = state->sheet;
683 char *base = g_strdup_printf (_("%s %%s Report"), sheet->name_unquoted);
684 gnm_solver_create_report (sol, base);
685 g_free (base);
689 static GnmSolverResult *
690 run_solver (SolverState *state, GnmSolverParameters *param)
692 GError *err = NULL;
693 gboolean ok;
694 GnmSheetRange sr;
695 GOUndo *undo = NULL;
696 GnmSolver *sol = NULL;
697 GnmValue const *vinput;
698 GtkWindow *top = GTK_WINDOW (gtk_widget_get_toplevel (state->dialog));
699 GnmSolverResult *res = NULL;
701 state->ref_count++;
703 sol = gnm_solver_factory_functional (param->options.algorithm,
704 state->wbcg)
705 ? gnm_solver_factory_create (param->options.algorithm, param)
706 : NULL;
707 if (!sol) {
708 go_gtk_notice_dialog (top, GTK_MESSAGE_ERROR,
709 _("The chosen solver is not functional."));
710 goto fail;
713 gtk_notebook_set_current_page (GTK_NOTEBOOK (state->notebook), -1);
715 state->run.solver = sol;
717 vinput = gnm_solver_param_get_input (param);
718 gnm_sheet_range_from_value (&sr, vinput);
719 if (!sr.sheet) sr.sheet = param->sheet;
720 undo = clipboard_copy_range_undo (sr.sheet, &sr.range);
722 g_signal_connect_swapped (G_OBJECT (sol),
723 "notify::status",
724 G_CALLBACK (cb_notify_status),
725 state);
726 g_signal_connect_swapped (G_OBJECT (sol),
727 "notify::reason",
728 G_CALLBACK (cb_notify_status),
729 state);
730 cb_notify_status (state);
732 g_signal_connect_swapped (G_OBJECT (sol),
733 "notify::result",
734 G_CALLBACK (cb_notify_result),
735 state);
736 cb_notify_result (state);
738 state->run.timer_source = g_timeout_add_seconds
739 (1, (GSourceFunc)cb_timer_tick, state);
740 cb_timer_tick (state);
742 /* ---------------------------------------- */
744 ok = gnm_solver_start (sol,
745 GNM_WBC (state->wbcg),
746 &err);
747 if (ok) {
748 state->run.in_main++;
749 go_cmd_context_set_sensitive (GO_CMD_CONTEXT (state->wbcg), FALSE);
750 gtk_main ();
751 go_cmd_context_set_sensitive (GO_CMD_CONTEXT (state->wbcg), TRUE);
752 state->run.in_main--;
753 ok = gnm_solver_has_solution (sol);
754 } else if (err) {
755 gnm_solver_set_reason (sol, err->message);
757 g_clear_error (&err);
759 remove_objective_value_source (state);
760 remove_timer_source (state);
762 /* ---------------------------------------- */
764 if (ok) {
765 GOUndo *redo;
767 gnm_solver_store_result (sol);
768 redo = clipboard_copy_range_undo (sr.sheet, &sr.range);
770 if (param->options.program_report ||
771 param->options.sensitivity_report) {
772 Workbook *wb = param->sheet->workbook;
773 GOUndo *undo_report, *redo_report;
775 undo_report = go_undo_binary_new
776 (wb,
777 workbook_sheet_state_new (wb),
778 (GOUndoBinaryFunc)workbook_sheet_state_restore,
779 NULL,
780 (GFreeFunc)workbook_sheet_state_free);
781 undo = go_undo_combine (undo, undo_report);
783 create_report (sol, state);
785 redo_report = go_undo_binary_new
786 (wb,
787 workbook_sheet_state_new (wb),
788 (GOUndoBinaryFunc)workbook_sheet_state_restore,
789 NULL,
790 (GFreeFunc)workbook_sheet_state_free);
791 redo = go_undo_combine (redo, redo_report);
794 cmd_generic (GNM_WBC (state->wbcg),
795 _("Running solver"),
796 undo, redo);
797 res = g_object_ref (sol->result);
798 undo = redo = NULL;
801 fail:
802 if (undo)
803 g_object_unref (undo);
805 if (state->run.solver) {
806 g_object_unref (state->run.solver);
807 state->run.solver = NULL;
810 unref_state (state);
812 return res;
816 static void
817 solver_add_scenario (SolverState *state, GnmSolverResult *res, gchar const *name)
819 GnmSolverParameters *param = state->sheet->solver_parameters;
820 GnmValue const *vinput;
821 GnmScenario *sc;
822 GnmSheetRange sr;
823 WorkbookControl *wbc = GNM_WBC (state->wbcg);
825 vinput = gnm_solver_param_get_input (param);
826 gnm_sheet_range_from_value (&sr, vinput);
828 sc = gnm_sheet_scenario_new (param->sheet, name);
829 switch (res->quality) {
830 case GNM_SOLVER_RESULT_OPTIMAL:
831 gnm_scenario_set_comment
832 (sc, _("Optimal solution created by solver.\n"));
833 break;
834 case GNM_SOLVER_RESULT_FEASIBLE:
835 gnm_scenario_set_comment
836 (sc, _("Feasible solution created by solver.\n"));
837 break;
838 default:
839 break;
841 gnm_scenario_add_area (sc, &sr);
843 cmd_scenario_add (wbc, sc, sc->sheet);
847 * cb_dialog_solve_clicked:
848 * @button:
849 * @state:
853 static void
854 cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
855 SolverState *state)
857 GnmSolverResult *res;
858 GnmSolverParameters *param = state->sheet->solver_parameters;
859 GError *err = NULL;
861 if (state->warning_dialog != NULL) {
862 gtk_widget_destroy (state->warning_dialog);
863 state->warning_dialog = NULL;
866 extract_settings (state);
868 if (!gnm_solver_param_valid (param, &err)) {
869 GtkWidget *top = gtk_widget_get_toplevel (state->dialog);
870 go_gtk_notice_dialog (GTK_WINDOW (top), GTK_MESSAGE_ERROR,
871 "%s", err->message);
872 goto out;
875 check_for_changed_options (state);
877 res = run_solver (state, param);
879 gnm_app_recalc ();
881 if (res != NULL) {
882 if ((res->quality == GNM_SOLVER_RESULT_OPTIMAL ||
883 res->quality == GNM_SOLVER_RESULT_FEASIBLE) &&
884 param->options.add_scenario)
885 solver_add_scenario (state, res,
886 param->options.scenario_name);
888 g_object_unref (res);
889 } else if (err) {
890 go_gtk_notice_nonmodal_dialog
891 (GTK_WINDOW (state->dialog),
892 &(state->warning_dialog),
893 GTK_MESSAGE_ERROR,
894 "%s", err->message);
897 out:
898 if (err)
899 g_error_free (err);
902 #define INIT_BOOL_ENTRY(name_, field_) \
903 do { \
904 GtkWidget *w_ = go_gtk_builder_get_widget (state->gui, (name_)); \
905 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w_), \
906 param->field_); \
907 } while (0)
911 * dialog_solver_init:
912 * @state:
914 * Create the dialog (guru).
917 static gboolean
918 dialog_solver_init (SolverState *state)
920 GtkGrid *grid;
921 GnmSolverParameters *param;
922 GtkCellRenderer *renderer;
923 GtkListStore *store;
924 GtkTreeViewColumn *column;
925 GSList *cl;
926 GnmCell *target_cell;
927 GnmValue const *input;
928 int i;
930 param = state->sheet->solver_parameters;
932 state->gui = gnm_gtk_builder_load ("res:ui/solver.ui", NULL, GO_CMD_CONTEXT (state->wbcg));
933 if (state->gui == NULL)
934 return TRUE;
936 state->dialog = go_gtk_builder_get_widget (state->gui, "Solver");
937 if (state->dialog == NULL)
938 return TRUE;
940 state->notebook = go_gtk_builder_get_widget (state->gui, "solver_notebook");
942 /* buttons */
943 state->solve_button = go_gtk_builder_get_widget (state->gui, "solvebutton");
944 g_signal_connect (G_OBJECT (state->solve_button), "clicked",
945 G_CALLBACK (cb_dialog_solve_clicked), state);
947 state->close_button = go_gtk_builder_get_widget (state->gui, "closebutton");
948 g_signal_connect (G_OBJECT (state->close_button), "clicked",
949 G_CALLBACK (cb_dialog_close_clicked), state);
951 state->help_button = go_gtk_builder_get_widget (state->gui, "helpbutton");
952 gnm_init_help_button (state->help_button, GNUMERIC_HELP_LINK_SOLVER);
954 state->add_button = go_gtk_builder_get_widget (state->gui, "addbutton");
955 gtk_button_set_alignment (GTK_BUTTON (state->add_button), 0.5, .5);
956 g_signal_connect_swapped (G_OBJECT (state->add_button), "clicked",
957 G_CALLBACK (cb_dialog_add_clicked), state);
959 state->change_button = go_gtk_builder_get_widget (state->gui,
960 "changebutton");
961 g_signal_connect (G_OBJECT (state->change_button), "clicked",
962 G_CALLBACK (cb_dialog_change_clicked), state);
964 state->delete_button = go_gtk_builder_get_widget (state->gui,
965 "deletebutton");
966 gtk_button_set_alignment (GTK_BUTTON (state->delete_button), 0.5, .5);
967 g_signal_connect (G_OBJECT (state->delete_button), "clicked",
968 G_CALLBACK (cb_dialog_delete_clicked), state);
970 state->stop_button = go_gtk_builder_get_widget (state->gui, "stopbutton");
971 g_signal_connect_swapped (G_OBJECT (state->stop_button),
972 "clicked", G_CALLBACK (cb_stop_solver),
973 state);
975 /* target_entry */
976 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui,
977 "parameter-grid"));
978 state->target_entry = gnm_expr_entry_new (state->wbcg, TRUE);
979 gnm_expr_entry_set_flags (state->target_entry,
980 GNM_EE_SINGLE_RANGE |
981 GNM_EE_FORCE_ABS_REF |
982 GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
983 gtk_widget_set_hexpand (GTK_WIDGET (state->target_entry), TRUE);
984 gtk_grid_attach (grid, GTK_WIDGET (state->target_entry), 1, 0, 2, 1);
985 gnm_editable_enters (GTK_WINDOW (state->dialog),
986 GTK_WIDGET (state->target_entry));
987 gtk_widget_show (GTK_WIDGET (state->target_entry));
988 g_signal_connect_after (G_OBJECT (state->target_entry), "changed",
989 G_CALLBACK (dialog_set_main_button_sensitivity),
990 state);
992 /* change_cell_entry */
993 state->change_cell_entry = gnm_expr_entry_new (state->wbcg, TRUE);
994 gnm_expr_entry_set_flags (state->change_cell_entry,
995 GNM_EE_SINGLE_RANGE |
996 GNM_EE_FORCE_ABS_REF |
997 GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
998 gtk_widget_set_hexpand (GTK_WIDGET (state->change_cell_entry), TRUE);
999 gtk_grid_attach (grid,
1000 GTK_WIDGET (state->change_cell_entry), 1, 2, 2, 1);
1001 gnm_editable_enters (GTK_WINDOW (state->dialog),
1002 GTK_WIDGET (state->change_cell_entry));
1003 gtk_widget_show (GTK_WIDGET (state->change_cell_entry));
1004 g_signal_connect_after (G_OBJECT (state->change_cell_entry), "changed",
1005 G_CALLBACK (dialog_set_main_button_sensitivity), state);
1007 /* Algorithm */
1008 state->algorithm_combo = GTK_COMBO_BOX
1009 (go_gtk_builder_get_widget (state->gui, "algorithm_combo"));
1010 renderer = (GtkCellRenderer*) gtk_cell_renderer_text_new();
1011 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (state->algorithm_combo), renderer, TRUE);
1012 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (state->algorithm_combo), renderer,
1013 "text", 0,
1014 NULL);
1015 fill_algorithm_combo (state, param->options.model_type);
1017 for (i = 0; model_type_group[i]; i++) {
1018 const char *bname = model_type_group[i];
1019 GtkWidget *w = go_gtk_builder_get_widget(state->gui, bname);
1020 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w),
1021 param->options.model_type ==
1022 (GnmSolverModelType)i);
1023 g_signal_connect (G_OBJECT (w), "clicked",
1024 G_CALLBACK (cb_dialog_model_type_clicked), state);
1027 /* Options */
1028 state->max_iter_entry = go_gtk_builder_get_widget (state->gui,
1029 "max_iter_entry");
1030 gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->max_iter_entry),
1031 param->options.max_iter);
1033 state->max_time_entry = go_gtk_builder_get_widget (state->gui,
1034 "max_time_entry");
1035 gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->max_time_entry),
1036 param->options.max_time_sec);
1038 state->gradient_order_entry = go_gtk_builder_get_widget (state->gui,
1039 "gradient_order_entry");
1040 gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->gradient_order_entry),
1041 param->options.gradient_order);
1043 /* lhs_entry */
1044 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui,
1045 "constraints-grid"));
1046 state->lhs.entry = gnm_expr_entry_new (state->wbcg, TRUE);
1047 gnm_expr_entry_set_flags (state->lhs.entry,
1048 GNM_EE_SINGLE_RANGE |
1049 GNM_EE_FORCE_ABS_REF |
1050 GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
1051 gtk_widget_set_hexpand (GTK_WIDGET (state->lhs.entry), TRUE);
1052 gtk_grid_attach (grid, GTK_WIDGET (state->lhs.entry), 0, 4, 1, 1);
1053 state->lhs.label = go_gtk_builder_get_widget (state->gui, "lhs_label");
1054 gtk_label_set_mnemonic_widget (GTK_LABEL (state->lhs.label),
1055 GTK_WIDGET (state->lhs.entry));
1056 gtk_widget_show (GTK_WIDGET (state->lhs.entry));
1057 g_signal_connect_after (G_OBJECT (state->lhs.entry),
1058 "changed",
1059 G_CALLBACK (dialog_set_sec_button_sensitivity), state);
1060 g_signal_connect_swapped (
1061 gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (state->lhs.entry)),
1062 "activate", G_CALLBACK (cb_dialog_add_clicked), state);
1064 /* rhs_entry */
1065 state->rhs.entry = gnm_expr_entry_new (state->wbcg, TRUE);
1066 gnm_expr_entry_set_flags (state->rhs.entry,
1067 GNM_EE_SINGLE_RANGE |
1068 GNM_EE_FORCE_ABS_REF |
1069 GNM_EE_SHEET_OPTIONAL |
1070 GNM_EE_CONSTANT_ALLOWED,
1071 GNM_EE_MASK);
1072 gtk_widget_set_hexpand (GTK_WIDGET (state->rhs.entry), TRUE);
1073 gtk_grid_attach (grid, GTK_WIDGET (state->rhs.entry), 2, 4, 1, 1);
1074 gtk_widget_show (GTK_WIDGET (state->rhs.entry));
1075 state->rhs.label = go_gtk_builder_get_widget (state->gui, "rhs_label");
1076 gtk_label_set_mnemonic_widget (
1077 GTK_LABEL (state->rhs.label), GTK_WIDGET (state->rhs.entry));
1078 g_signal_connect_after (G_OBJECT (state->rhs.entry),
1079 "changed",
1080 G_CALLBACK (dialog_set_sec_button_sensitivity), state);
1081 g_signal_connect_swapped (
1082 gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (state->rhs.entry)),
1083 "activate", G_CALLBACK (cb_dialog_add_clicked), state);
1085 /* type_menu */
1086 state->type_combo = GTK_COMBO_BOX
1087 (go_gtk_builder_get_widget (state->gui, "type_menu"));
1088 gtk_combo_box_set_active (state->type_combo, 0);
1089 g_signal_connect (state->type_combo, "changed",
1090 G_CALLBACK (dialog_set_sec_button_sensitivity),
1091 state);
1093 /* constraint_list */
1094 state->constraint_list = GTK_TREE_VIEW (go_gtk_builder_get_widget
1095 (state->gui, "constraint_list"));
1097 state->constr = NULL;
1098 g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (state->constraint_list)), "changed",
1099 G_CALLBACK (constraint_select_click), state);
1100 gtk_tree_view_set_reorderable (state->constraint_list, TRUE);
1101 store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
1102 gtk_tree_view_set_model (state->constraint_list, GTK_TREE_MODEL(store));
1103 renderer = gtk_cell_renderer_text_new ();
1104 column = gtk_tree_view_column_new_with_attributes (
1105 _("Subject to the Constraints:"),
1106 renderer, "text", 0, NULL);
1107 gtk_tree_view_column_set_expand (column, TRUE);
1108 gtk_tree_view_append_column (state->constraint_list, column);
1111 GtkWidget *w = GTK_WIDGET (state->constraint_list);
1112 int width, height, vsep;
1113 PangoLayout *layout =
1114 gtk_widget_create_pango_layout (w, "Mg19");
1116 gtk_widget_style_get (w,
1117 "vertical_separator", &vsep,
1118 NULL);
1120 pango_layout_get_pixel_size (layout, &width, &height);
1121 gtk_widget_set_size_request (w,
1123 (2 * height + vsep) * (4 + 1));
1124 g_object_unref (layout);
1127 /* Loading the old solver specs... from param */
1129 for (cl = param->constraints; cl; cl = cl->next) {
1130 GnmSolverConstraint const *c = cl->data;
1131 GtkTreeIter iter;
1132 char *str;
1134 gtk_list_store_append (store, &iter);
1135 str = gnm_solver_constraint_as_str (c, state->sheet);
1136 gtk_list_store_set (store, &iter, 0, str, 1, c, -1);
1137 g_free (str);
1139 g_object_unref (store);
1141 INIT_BOOL_ENTRY ("autoscale_button", options.automatic_scaling);
1142 INIT_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
1143 INIT_BOOL_ENTRY ("all_int_button", options.assume_discrete);
1144 INIT_BOOL_ENTRY ("program", options.program_report);
1145 INIT_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
1147 input = gnm_solver_param_get_input (param);
1148 if (input != NULL)
1149 gnm_expr_entry_load_from_text (state->change_cell_entry,
1150 value_peek_string (input));
1151 target_cell = gnm_solver_param_get_target_cell (param);
1152 if (target_cell)
1153 gnm_expr_entry_load_from_text (state->target_entry,
1154 cell_name (target_cell));
1155 else {
1156 SheetView *sv = wb_control_cur_sheet_view
1157 (GNM_WBC (state->wbcg));
1158 if (sv) {
1159 GnmRange first = {sv->edit_pos, sv->edit_pos};
1160 gnm_expr_entry_load_from_range (state->target_entry,
1161 state->sheet, &first);
1165 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1166 go_gtk_builder_get_widget(state->gui, "max_button")),
1167 param->problem_type == GNM_SOLVER_MAXIMIZE);
1168 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1169 go_gtk_builder_get_widget(state->gui, "min_button")),
1170 param->problem_type == GNM_SOLVER_MINIMIZE);
1171 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1172 go_gtk_builder_get_widget(state->gui, "no_scenario")),
1173 ! param->options.add_scenario);
1174 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1175 go_gtk_builder_get_widget(state->gui, "optimal_scenario")),
1176 param->options.add_scenario);
1178 state->scenario_name_entry = go_gtk_builder_get_widget
1179 (state->gui, "scenario_name_entry");
1180 gtk_entry_set_text (GTK_ENTRY (state->scenario_name_entry),
1181 param->options.scenario_name);
1183 state->run.status_widget = go_gtk_builder_get_widget (state->gui, "solver_status_label");
1184 state->run.problem_status_widget = go_gtk_builder_get_widget (state->gui, "problem_status_label");
1185 state->run.objective_value_widget = go_gtk_builder_get_widget (state->gui, "objective_value_label");
1186 state->run.timer_widget = go_gtk_builder_get_widget (state->gui, "elapsed_time_label");
1187 state->run.spinner = go_gtk_builder_get_widget (state->gui, "run_spinner");
1190 /* Done */
1191 gnm_expr_entry_grab_focus (state->target_entry, FALSE);
1192 wbcg_set_entry (state->wbcg, state->target_entry);
1194 dialog_set_main_button_sensitivity (NULL, state);
1195 dialog_set_sec_button_sensitivity (NULL, state);
1197 /* dialog */
1198 wbc_gtk_attach_guru (state->wbcg, state->dialog);
1200 g_signal_connect_swapped (G_OBJECT (state->dialog),
1201 "destroy",
1202 G_CALLBACK (cb_dialog_solver_destroy),
1203 state);
1204 g_object_set_data_full (G_OBJECT (state->dialog),
1205 "state", state,
1206 (GDestroyNotify)unref_state);
1208 return FALSE;
1212 * dialog_solver:
1213 * @wbcg:
1214 * @sheet:
1216 * Create the dialog (guru).
1219 void
1220 dialog_solver (WBCGtk *wbcg, Sheet *sheet)
1222 SolverState *state;
1223 GnmSolverParameters *old_params = sheet->solver_parameters;
1224 gboolean got_it;
1225 int pass;
1227 /* Only pop up one copy per workbook */
1228 if (gnm_dialog_raise_if_exists (wbcg, SOLVER_KEY))
1229 return;
1232 * First time around, pick a functional algorithm preferably one we
1233 * can determine is functional without asking the user anything.
1235 got_it = gnm_solver_factory_functional (old_params->options.algorithm,
1236 NULL);
1237 for (pass = 1; !got_it && pass <= 2; pass++) {
1238 GSList *l;
1239 WBCGtk *wbcg2 = pass == 2 ? wbcg : NULL;
1241 for (l = gnm_solver_db_get (); l; l = l->next) {
1242 GnmSolverFactory *factory = l->data;
1243 if (old_params->options.model_type != factory->type)
1244 continue;
1245 if (gnm_solver_factory_functional (factory, wbcg2)) {
1246 got_it = TRUE;
1247 gnm_solver_param_set_algorithm (old_params,
1248 factory);
1249 break;
1254 state = g_new0 (SolverState, 1);
1255 state->ref_count = 1;
1256 state->wbcg = wbcg;
1257 state->sheet = sheet;
1258 state->warning_dialog = NULL;
1259 state->orig_params = gnm_solver_param_dup (sheet->solver_parameters,
1260 sheet);
1262 if (dialog_solver_init (state)) {
1263 go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
1264 _("Could not create the Solver dialog."));
1265 unref_state (state);
1266 return;
1269 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog), wbcg,
1270 GNM_DIALOG_DESTROY_SHEET_REMOVED);
1272 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1273 SOLVER_KEY);
1275 gtk_widget_show (state->dialog);