Compilation: don't compile dialogs separately.
[gnumeric.git] / src / dialogs / dialog-solver.c
blobf22988e95f4f34bc9b6dff199a2161d0870d74ef
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * dialog-solver.c:
5 * Author:
6 * Jukka-Pekka Iivonen <iivonen@iki.fi>
8 * (C) Copyright 2000, 2002 by Jukka-Pekka Iivonen <iivonen@iki.fi>
9 * (C) Copyright 2009-2013 Morten Welinder (terra@gnome.org)
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, see <https://www.gnu.org/licenses/>.
25 #include <gnumeric-config.h>
26 #include <glib/gi18n-lib.h>
27 #include <gnumeric.h>
28 #include "dialogs.h"
29 #include "help.h"
31 #include <gui-util.h>
32 #include <func.h>
33 #include <tool-dialogs.h>
34 #include <value.h>
35 #include <cell.h>
36 #include <sheet.h>
37 #include <sheet-view.h>
38 #include <expr.h>
39 #include <wbc-gtk.h>
40 #include <workbook.h>
41 #include <parse-util.h>
42 #include <ranges.h>
43 #include <commands.h>
44 #include <clipboard.h>
45 #include <selection.h>
46 #include <application.h>
47 #include <tools/gnm-solver.h>
48 #include <widgets/gnumeric-expr-entry.h>
50 #include <gtk/gtk.h>
51 #include <goffice/goffice.h>
52 #include <string.h>
53 #include <scenarios.h>
55 #define SOLVER_KEY "solver-dialog"
57 typedef struct {
58 int ref_count;
59 GtkBuilder *gui;
60 GtkWidget *dialog;
61 GtkWidget *notebook;
62 GnmExprEntry *target_entry;
63 GnmExprEntry *change_cell_entry;
64 GtkWidget *max_iter_entry;
65 GtkWidget *max_time_entry;
66 GtkWidget *gradient_order_entry;
67 GtkWidget *solve_button;
68 GtkWidget *close_button;
69 GtkWidget *stop_button;
70 GtkWidget *help_button;
71 GtkWidget *add_button;
72 GtkWidget *change_button;
73 GtkWidget *delete_button;
74 GtkWidget *scenario_name_entry;
75 struct {
76 GnmExprEntry*entry;
77 GtkWidget *label;
78 } lhs, rhs;
79 GtkComboBox *type_combo;
80 GtkComboBox *algorithm_combo;
81 GtkTreeView *constraint_list;
82 GnmSolverConstraint *constr;
83 GtkWidget *warning_dialog;
85 struct {
86 GnmSolver *solver;
87 GtkWidget *timer_widget;
88 guint timer_source;
89 GtkWidget *status_widget;
90 GtkWidget *problem_status_widget;
91 GtkWidget *objective_value_widget;
92 guint obj_val_source;
93 GtkWidget *spinner;
94 guint in_main;
95 } run;
97 Sheet *sheet;
98 WBCGtk *wbcg;
100 GnmSolverParameters *orig_params;
101 } SolverState;
104 static char const * const problem_type_group[] = {
105 "min_button",
106 "max_button",
107 "equal_to_button",
108 NULL
111 static char const * const model_type_group[] = {
112 "lp_model_button",
113 "qp_model_button",
114 "nlp_model_button",
115 NULL
118 static void
119 constraint_fill (GnmSolverConstraint *c, SolverState *state)
121 Sheet *sheet = state->sheet;
123 c->type = gtk_combo_box_get_active (state->type_combo);
125 gnm_solver_constraint_set_lhs
127 gnm_expr_entry_parse_as_value (state->lhs.entry, sheet));
129 gnm_solver_constraint_set_rhs
131 gnm_solver_constraint_has_rhs (c)
132 ? gnm_expr_entry_parse_as_value (state->rhs.entry, sheet)
133 : NULL);
136 static gboolean
137 dialog_set_sec_button_sensitivity (G_GNUC_UNUSED GtkWidget *dummy,
138 SolverState *state)
140 gboolean select_ready = (state->constr != NULL);
141 GnmSolverConstraint *test = gnm_solver_constraint_new (NULL);
142 gboolean ready, has_rhs;
143 GnmSolverParameters const *param = state->sheet->solver_parameters;
145 constraint_fill (test, state);
146 ready = gnm_solver_constraint_valid (test, param);
147 has_rhs = gnm_solver_constraint_has_rhs (test);
148 gnm_solver_constraint_free (test);
150 gtk_widget_set_sensitive (state->add_button, ready);
151 gtk_widget_set_sensitive (state->change_button, select_ready && ready);
152 gtk_widget_set_sensitive (state->delete_button, select_ready);
153 gtk_widget_set_sensitive (GTK_WIDGET (state->rhs.entry), has_rhs);
154 gtk_widget_set_sensitive (GTK_WIDGET (state->rhs.label), has_rhs);
156 /* Return TRUE iff the current constraint is valid. */
157 return ready;
160 static void
161 constraint_select_click (GtkTreeSelection *Selection,
162 SolverState * state)
164 GtkTreeModel *store;
165 GtkTreeIter iter;
166 GnmSolverConstraint const *c;
167 GnmValue const *lhs, *rhs;
169 if (gtk_tree_selection_get_selected (Selection, &store, &iter))
170 gtk_tree_model_get (store, &iter, 1, &state->constr, -1);
171 else
172 state->constr = NULL;
173 dialog_set_sec_button_sensitivity (NULL, state);
175 if (state->constr == NULL)
176 return; /* Fail? */
177 c = state->constr;
179 lhs = gnm_solver_constraint_get_lhs (c);
180 if (lhs) {
181 GnmExprTop const *texpr =
182 gnm_expr_top_new_constant (value_dup (lhs));
183 GnmParsePos pp;
185 gnm_expr_entry_load_from_expr
186 (state->lhs.entry,
187 texpr,
188 parse_pos_init_sheet (&pp, state->sheet));
189 gnm_expr_top_unref (texpr);
190 } else
191 gnm_expr_entry_load_from_text (state->lhs.entry, "");
193 rhs = gnm_solver_constraint_get_rhs (c);
194 if (rhs && gnm_solver_constraint_has_rhs (c)) {
195 GnmExprTop const *texpr =
196 gnm_expr_top_new_constant (value_dup (rhs));
197 GnmParsePos pp;
199 gnm_expr_entry_load_from_expr
200 (state->rhs.entry,
201 texpr,
202 parse_pos_init_sheet (&pp, state->sheet));
203 gnm_expr_top_unref (texpr);
204 } else
205 gnm_expr_entry_load_from_text (state->rhs.entry, "");
207 gtk_combo_box_set_active (state->type_combo, c->type);
211 * cb_dialog_delete_clicked:
212 * @button:
213 * @state:
217 static void
218 cb_dialog_delete_clicked (G_GNUC_UNUSED GtkWidget *button, SolverState *state)
220 if (state->constr != NULL) {
221 GtkTreeIter iter;
222 GtkTreeModel *store;
223 GnmSolverParameters *param = state->sheet->solver_parameters;
225 param->constraints =
226 g_slist_remove (param->constraints, state->constr);
227 gnm_solver_constraint_free (state->constr);
228 state->constr = NULL;
230 if (gtk_tree_selection_get_selected (
231 gtk_tree_view_get_selection (state->constraint_list),
232 &store, &iter))
233 gtk_list_store_remove ((GtkListStore*)store, &iter);
237 static void
238 constraint_fill_row (SolverState *state, GtkListStore *store, GtkTreeIter *iter)
240 char *text;
241 GnmSolverConstraint *c = state->constr;
243 constraint_fill (c, state);
245 text = gnm_solver_constraint_as_str (c, state->sheet);
246 gtk_list_store_set (store, iter, 0, text, 1, c, -1);
247 g_free (text);
248 gtk_tree_selection_select_iter (gtk_tree_view_get_selection (state->constraint_list), iter);
251 static void
252 cb_dialog_add_clicked (SolverState *state)
254 if (dialog_set_sec_button_sensitivity (NULL, state)) {
255 GtkTreeIter iter;
256 GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (state->constraint_list));
257 GnmSolverParameters *param = state->sheet->solver_parameters;
259 gtk_list_store_append (store, &iter);
260 state->constr = gnm_solver_constraint_new (state->sheet);
261 constraint_fill_row (state, store, &iter);
262 param->constraints =
263 g_slist_append (param->constraints, state->constr);
267 static void
268 cb_dialog_change_clicked (GtkWidget *button, SolverState *state)
270 if (state->constr != NULL) {
271 GtkTreeIter iter;
272 GtkTreeModel *store;
274 if (gtk_tree_selection_get_selected (
275 gtk_tree_view_get_selection (state->constraint_list),
276 &store, &iter))
277 constraint_fill_row (state, (GtkListStore *)store, &iter);
281 static void
282 dialog_set_main_button_sensitivity (G_GNUC_UNUSED GtkWidget *dummy,
283 SolverState *state)
285 gboolean ready;
287 ready = gnm_expr_entry_is_cell_ref (state->target_entry, state->sheet,
288 FALSE)
289 && gnm_expr_entry_is_cell_ref (state->change_cell_entry,
290 state->sheet, TRUE);
291 gtk_widget_set_sensitive (state->solve_button, ready);
294 static gboolean
295 fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
297 GtkListStore *store =
298 gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
299 GSList *solvers, *l;
300 int sel = 0, i;
301 GnmSolverParameters *param =state->sheet->solver_parameters;
303 gtk_combo_box_set_model (state->algorithm_combo, GTK_TREE_MODEL (store));
305 l = NULL;
306 for (solvers = gnm_solver_db_get (); solvers; solvers = solvers->next) {
307 GnmSolverFactory *entry = solvers->data;
308 if (type != entry->type)
309 continue;
310 l = g_slist_prepend (l, entry);
312 solvers = g_slist_reverse (l);
314 gtk_widget_set_sensitive (GTK_WIDGET (state->solve_button),
315 solvers != NULL);
316 if (!solvers)
317 return FALSE;
319 for (l = solvers, i = 0; l; l = l->next, i++) {
320 GnmSolverFactory *factory = l->data;
321 GtkTreeIter iter;
323 if (param->options.algorithm == factory)
324 sel = i;
326 gtk_list_store_append (store, &iter);
327 gtk_list_store_set (store, &iter,
328 0, factory->name,
329 1, factory,
330 -1);
332 g_slist_free (solvers);
334 gtk_combo_box_set_active (state->algorithm_combo, sel);
336 g_object_unref (store);
338 return TRUE;
341 static void
342 cb_dialog_model_type_clicked (G_GNUC_UNUSED GtkWidget *button,
343 SolverState *state)
345 GnmSolverModelType type;
346 gboolean any;
348 type = gnm_gui_group_value (state->gui, model_type_group);
349 any = fill_algorithm_combo (state, type);
351 if (!any) {
352 go_gtk_notice_nonmodal_dialog
353 (GTK_WINDOW (state->dialog),
354 &(state->warning_dialog),
355 GTK_MESSAGE_INFO,
356 _("Looking for a subject for your thesis? "
357 "Maybe you would like to write a solver for "
358 "Gnumeric?"));
362 static void
363 unref_state (SolverState *state)
365 state->ref_count--;
366 if (state->ref_count > 0)
367 return;
369 if (state->orig_params)
370 g_object_unref (state->orig_params);
371 g_free (state);
374 static GOUndo *
375 set_params (Sheet *sheet, GnmSolverParameters *params)
377 return go_undo_binary_new
378 (sheet, g_object_ref (params),
379 (GOUndoBinaryFunc)gnm_sheet_set_solver_params,
380 NULL, g_object_unref);
383 #define GET_BOOL_ENTRY(name_, field_) \
384 do { \
385 GtkWidget *w_ = go_gtk_builder_get_widget (state->gui, (name_)); \
386 param->field_ = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w_)); \
387 } while (0)
389 static void
390 extract_settings (SolverState *state)
392 GnmSolverParameters *param = state->sheet->solver_parameters;
393 GtkTreeIter iter;
394 GnmValue *target_range;
395 GnmValue *input_range;
396 GnmSolverFactory *factory = NULL;
398 target_range = gnm_expr_entry_parse_as_value (state->target_entry,
399 state->sheet);
400 input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
401 state->sheet);
403 gnm_solver_param_set_input (param, input_range);
405 gnm_solver_param_set_target (param,
406 target_range
407 ? &target_range->v_range.cell.a
408 : NULL);
410 param->problem_type =
411 gnm_gui_group_value (state->gui, problem_type_group);
412 param->options.model_type =
413 gnm_gui_group_value (state->gui, model_type_group);
415 if (gtk_combo_box_get_active_iter (state->algorithm_combo, &iter)) {
416 gtk_tree_model_get (gtk_combo_box_get_model (state->algorithm_combo),
417 &iter, 1, &factory, -1);
418 gnm_solver_param_set_algorithm (param, factory);
419 } else
420 gnm_solver_param_set_algorithm (param, NULL);
422 param->options.max_iter = gtk_spin_button_get_value
423 (GTK_SPIN_BUTTON (state->max_iter_entry));
424 param->options.max_time_sec = gtk_spin_button_get_value
425 (GTK_SPIN_BUTTON (state->max_time_entry));
426 param->options.gradient_order = gtk_spin_button_get_value
427 (GTK_SPIN_BUTTON (state->gradient_order_entry));
429 GET_BOOL_ENTRY ("autoscale_button", options.automatic_scaling);
430 GET_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
431 GET_BOOL_ENTRY ("all_int_button", options.assume_discrete);
432 GET_BOOL_ENTRY ("program", options.program_report);
433 GET_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
435 g_free (param->options.scenario_name);
436 param->options.scenario_name = g_strdup
437 (gtk_entry_get_text (GTK_ENTRY (state->scenario_name_entry)));
439 GET_BOOL_ENTRY ("optimal_scenario", options.add_scenario);
441 value_release (target_range);
444 #undef GET_BOOL_ENTRY
446 static void
447 check_for_changed_options (SolverState *state)
449 Sheet *sheet = state->sheet;
451 if (!gnm_solver_param_equal (sheet->solver_parameters,
452 state->orig_params)) {
453 GOUndo *undo = set_params (sheet, state->orig_params);
454 GOUndo *redo = set_params (sheet, sheet->solver_parameters);
455 cmd_generic (GNM_WBC (state->wbcg),
456 _("Changing solver parameters"),
457 undo, redo);
459 g_object_unref (state->orig_params);
460 state->orig_params =
461 gnm_solver_param_dup (sheet->solver_parameters,
462 sheet);
466 static void
467 cb_dialog_solver_destroy (SolverState *state)
469 g_return_if_fail (state != NULL);
471 if (state->run.solver) {
472 gnm_solver_stop (state->run.solver, NULL);
473 g_object_set (state->run.solver, "result", NULL, NULL);
476 extract_settings (state);
478 check_for_changed_options (state);
480 if (state->gui != NULL) {
481 g_object_unref (state->gui);
482 state->gui = NULL;
485 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
487 state->dialog = NULL;
490 static void
491 cb_dialog_close_clicked (G_GNUC_UNUSED GtkWidget *button,
492 SolverState *state)
494 gtk_widget_destroy (state->dialog);
497 static void
498 cb_stop_solver (SolverState *state)
500 GnmSolver *sol = state->run.solver;
502 switch (sol->status) {
503 case GNM_SOLVER_STATUS_RUNNING: {
504 gboolean ok = gnm_solver_stop (sol, NULL);
505 if (!ok) {
506 g_warning ("Failed to stop solver!");
508 g_object_set (sol, "result", NULL, NULL);
509 break;
512 default:
513 break;
517 static void
518 remove_timer_source (SolverState *state)
520 if (state->run.timer_source) {
521 g_source_remove (state->run.timer_source);
522 state->run.timer_source = 0;
526 static void
527 remove_objective_value_source (SolverState *state)
529 if (state->run.obj_val_source) {
530 g_source_remove (state->run.obj_val_source);
531 state->run.obj_val_source = 0;
535 static void
536 update_obj_value (SolverState *state)
538 GnmSolver *sol = state->run.solver;
539 GnmSolverResult *r = sol->result;
540 char *valtxt;
541 const char *txt;
543 switch (r ? r->quality : GNM_SOLVER_RESULT_NONE) {
544 default:
545 case GNM_SOLVER_RESULT_NONE:
546 txt = "";
547 break;
549 case GNM_SOLVER_RESULT_FEASIBLE:
550 txt = _("Feasible");
551 break;
553 case GNM_SOLVER_RESULT_OPTIMAL:
554 txt = _("Optimal");
555 break;
557 case GNM_SOLVER_RESULT_INFEASIBLE:
558 txt = _("Infeasible");
559 break;
561 case GNM_SOLVER_RESULT_UNBOUNDED:
562 txt = _("Unbounded");
563 break;
565 gtk_label_set_text (GTK_LABEL (state->run.problem_status_widget), txt);
567 if (gnm_solver_has_solution (sol)) {
568 txt = valtxt = gnm_format_value (go_format_general (),
569 r->value);
570 } else {
571 valtxt = NULL;
572 txt = "";
575 gtk_label_set_text (GTK_LABEL (state->run.objective_value_widget),
576 txt);
577 g_free (valtxt);
579 remove_objective_value_source (state);
582 static void
583 cb_notify_status (SolverState *state)
585 GnmSolver *sol = state->run.solver;
586 const char *text;
587 gboolean finished = gnm_solver_finished (sol);
588 gboolean running = FALSE;
590 switch (sol->status) {
591 case GNM_SOLVER_STATUS_READY:
592 text = _("Ready");
593 break;
594 case GNM_SOLVER_STATUS_PREPARING:
595 text = _("Preparing");
596 break;
597 case GNM_SOLVER_STATUS_PREPARED:
598 text = _("Prepared");
599 break;
600 case GNM_SOLVER_STATUS_RUNNING:
601 text = _("Running");
602 running = TRUE;
603 break;
604 case GNM_SOLVER_STATUS_DONE:
605 text = _("Done");
606 break;
607 default:
608 case GNM_SOLVER_STATUS_ERROR:
609 text = _("Error");
610 break;
611 case GNM_SOLVER_STATUS_CANCELLED:
612 text = _("Cancelled");
613 break;
616 if (sol->reason) {
617 char *text2 = g_strconcat (text,
618 " (", sol->reason, ")",
619 NULL);
620 gtk_label_set_text (GTK_LABEL (state->run.status_widget),
621 text2);
622 g_free (text2);
623 } else {
624 gtk_label_set_text (GTK_LABEL (state->run.status_widget), text);
627 gtk_widget_set_visible (state->run.spinner, running);
628 gtk_widget_set_visible (state->stop_button, !finished);
629 gtk_widget_set_sensitive (state->solve_button, finished);
630 gtk_widget_set_sensitive (state->close_button, finished);
632 if (state->run.obj_val_source)
633 update_obj_value (state);
635 if (finished) {
636 remove_timer_source (state);
637 if (state->run.in_main)
638 gtk_main_quit ();
642 static gboolean
643 cb_obj_val_tick (SolverState *state)
645 state->run.obj_val_source = 0;
646 update_obj_value (state);
647 return FALSE;
650 static void
651 cb_notify_result (SolverState *state)
653 if (state->run.obj_val_source == 0)
654 state->run.obj_val_source = g_timeout_add
655 (100, (GSourceFunc)cb_obj_val_tick, state);
658 static gboolean
659 cb_timer_tick (SolverState *state)
661 GnmSolver *sol = state->run.solver;
662 double dsecs = gnm_solver_elapsed (sol);
663 int secs = (int)CLAMP (dsecs, 0, INT_MAX);
664 int hh = secs / 3600;
665 int mm = secs / 60 % 60;
666 int ss = secs % 60;
667 char *txt = hh
668 ? g_strdup_printf ("%02d:%02d:%02d", hh, mm, ss)
669 : g_strdup_printf ("%02d:%02d", mm, ss);
671 gtk_label_set_text (GTK_LABEL (state->run.timer_widget), txt);
672 g_free (txt);
674 if (gnm_solver_check_timeout (sol)) {
675 cb_notify_status (state);
678 return TRUE;
681 static void
682 create_report (GnmSolver *sol, SolverState *state)
684 Sheet *sheet = state->sheet;
685 char *base = g_strdup_printf (_("%s %%s Report"), sheet->name_unquoted);
686 gnm_solver_create_report (sol, base);
687 g_free (base);
691 static GnmSolverResult *
692 run_solver (SolverState *state, GnmSolverParameters *param)
694 GError *err = NULL;
695 gboolean ok;
696 GnmSheetRange sr;
697 GOUndo *undo = NULL;
698 GnmSolver *sol = NULL;
699 GnmValue const *vinput;
700 GtkWindow *top = GTK_WINDOW (gtk_widget_get_toplevel (state->dialog));
701 GnmSolverResult *res = NULL;
703 state->ref_count++;
705 sol = gnm_solver_factory_functional (param->options.algorithm,
706 state->wbcg)
707 ? gnm_solver_factory_create (param->options.algorithm, param)
708 : NULL;
709 if (!sol) {
710 go_gtk_notice_dialog (top, GTK_MESSAGE_ERROR,
711 _("The chosen solver is not functional."));
712 goto fail;
715 gtk_notebook_set_current_page (GTK_NOTEBOOK (state->notebook), -1);
717 state->run.solver = sol;
719 vinput = gnm_solver_param_get_input (param);
720 gnm_sheet_range_from_value (&sr, vinput);
721 if (!sr.sheet) sr.sheet = param->sheet;
722 undo = clipboard_copy_range_undo (sr.sheet, &sr.range);
724 g_signal_connect_swapped (G_OBJECT (sol),
725 "notify::status",
726 G_CALLBACK (cb_notify_status),
727 state);
728 g_signal_connect_swapped (G_OBJECT (sol),
729 "notify::reason",
730 G_CALLBACK (cb_notify_status),
731 state);
732 cb_notify_status (state);
734 g_signal_connect_swapped (G_OBJECT (sol),
735 "notify::result",
736 G_CALLBACK (cb_notify_result),
737 state);
738 cb_notify_result (state);
740 state->run.timer_source = g_timeout_add_seconds
741 (1, (GSourceFunc)cb_timer_tick, state);
742 cb_timer_tick (state);
744 /* ---------------------------------------- */
746 ok = gnm_solver_start (sol,
747 GNM_WBC (state->wbcg),
748 &err);
749 if (ok) {
750 state->run.in_main++;
751 go_cmd_context_set_sensitive (GO_CMD_CONTEXT (state->wbcg), FALSE);
752 gtk_main ();
753 go_cmd_context_set_sensitive (GO_CMD_CONTEXT (state->wbcg), TRUE);
754 state->run.in_main--;
755 ok = gnm_solver_has_solution (sol);
756 } else if (err) {
757 gnm_solver_set_reason (sol, err->message);
759 g_clear_error (&err);
761 remove_objective_value_source (state);
762 remove_timer_source (state);
764 /* ---------------------------------------- */
766 if (ok) {
767 GOUndo *redo;
769 gnm_solver_store_result (sol);
770 redo = clipboard_copy_range_undo (sr.sheet, &sr.range);
772 if (param->options.program_report ||
773 param->options.sensitivity_report) {
774 Workbook *wb = param->sheet->workbook;
775 GOUndo *undo_report, *redo_report;
777 undo_report = go_undo_binary_new
778 (wb,
779 workbook_sheet_state_new (wb),
780 (GOUndoBinaryFunc)workbook_sheet_state_restore,
781 NULL,
782 (GFreeFunc)workbook_sheet_state_free);
783 undo = go_undo_combine (undo, undo_report);
785 create_report (sol, state);
787 redo_report = go_undo_binary_new
788 (wb,
789 workbook_sheet_state_new (wb),
790 (GOUndoBinaryFunc)workbook_sheet_state_restore,
791 NULL,
792 (GFreeFunc)workbook_sheet_state_free);
793 redo = go_undo_combine (redo, redo_report);
796 cmd_generic (GNM_WBC (state->wbcg),
797 _("Running solver"),
798 undo, redo);
799 res = g_object_ref (sol->result);
800 undo = redo = NULL;
803 fail:
804 if (undo)
805 g_object_unref (undo);
807 if (state->run.solver) {
808 g_object_unref (state->run.solver);
809 state->run.solver = NULL;
812 unref_state (state);
814 return res;
818 static void
819 solver_add_scenario (SolverState *state, GnmSolverResult *res, gchar const *name)
821 GnmSolverParameters *param = state->sheet->solver_parameters;
822 GnmValue const *vinput;
823 GnmScenario *sc;
824 GnmSheetRange sr;
825 WorkbookControl *wbc = GNM_WBC (state->wbcg);
827 vinput = gnm_solver_param_get_input (param);
828 gnm_sheet_range_from_value (&sr, vinput);
830 sc = gnm_sheet_scenario_new (param->sheet, name);
831 switch (res->quality) {
832 case GNM_SOLVER_RESULT_OPTIMAL:
833 gnm_scenario_set_comment
834 (sc, _("Optimal solution created by solver.\n"));
835 break;
836 case GNM_SOLVER_RESULT_FEASIBLE:
837 gnm_scenario_set_comment
838 (sc, _("Feasible solution created by solver.\n"));
839 break;
840 default:
841 break;
843 gnm_scenario_add_area (sc, &sr);
845 cmd_scenario_add (wbc, sc, sc->sheet);
849 * cb_dialog_solve_clicked:
850 * @button:
851 * @state:
855 static void
856 cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
857 SolverState *state)
859 GnmSolverResult *res;
860 GnmSolverParameters *param = state->sheet->solver_parameters;
861 GError *err = NULL;
863 if (state->warning_dialog != NULL) {
864 gtk_widget_destroy (state->warning_dialog);
865 state->warning_dialog = NULL;
868 extract_settings (state);
870 if (!gnm_solver_param_valid (param, &err)) {
871 GtkWidget *top = gtk_widget_get_toplevel (state->dialog);
872 go_gtk_notice_dialog (GTK_WINDOW (top), GTK_MESSAGE_ERROR,
873 "%s", err->message);
874 goto out;
877 check_for_changed_options (state);
879 res = run_solver (state, param);
881 gnm_app_recalc ();
883 if (res != NULL) {
884 if ((res->quality == GNM_SOLVER_RESULT_OPTIMAL ||
885 res->quality == GNM_SOLVER_RESULT_FEASIBLE) &&
886 param->options.add_scenario)
887 solver_add_scenario (state, res,
888 param->options.scenario_name);
890 g_object_unref (res);
891 } else if (err) {
892 go_gtk_notice_nonmodal_dialog
893 (GTK_WINDOW (state->dialog),
894 &(state->warning_dialog),
895 GTK_MESSAGE_ERROR,
896 "%s", err->message);
899 out:
900 if (err)
901 g_error_free (err);
904 #define INIT_BOOL_ENTRY(name_, field_) \
905 do { \
906 GtkWidget *w_ = go_gtk_builder_get_widget (state->gui, (name_)); \
907 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w_), \
908 param->field_); \
909 } while (0)
913 * dialog_solver_init:
914 * @state:
916 * Create the dialog (guru).
919 static gboolean
920 dialog_solver_init (SolverState *state)
922 GtkGrid *grid;
923 GnmSolverParameters *param;
924 GtkCellRenderer *renderer;
925 GtkListStore *store;
926 GtkTreeViewColumn *column;
927 GSList *cl;
928 GnmCell *target_cell;
929 GnmValue const *input;
930 int i;
932 param = state->sheet->solver_parameters;
934 state->gui = gnm_gtk_builder_load ("res:ui/solver.ui", NULL, GO_CMD_CONTEXT (state->wbcg));
935 if (state->gui == NULL)
936 return TRUE;
938 state->dialog = go_gtk_builder_get_widget (state->gui, "Solver");
939 if (state->dialog == NULL)
940 return TRUE;
942 state->notebook = go_gtk_builder_get_widget (state->gui, "solver_notebook");
944 /* buttons */
945 state->solve_button = go_gtk_builder_get_widget (state->gui, "solvebutton");
946 g_signal_connect (G_OBJECT (state->solve_button), "clicked",
947 G_CALLBACK (cb_dialog_solve_clicked), state);
949 state->close_button = go_gtk_builder_get_widget (state->gui, "closebutton");
950 g_signal_connect (G_OBJECT (state->close_button), "clicked",
951 G_CALLBACK (cb_dialog_close_clicked), state);
953 state->help_button = go_gtk_builder_get_widget (state->gui, "helpbutton");
954 gnm_init_help_button (state->help_button, GNUMERIC_HELP_LINK_SOLVER);
956 state->add_button = go_gtk_builder_get_widget (state->gui, "addbutton");
957 gtk_button_set_alignment (GTK_BUTTON (state->add_button), 0.5, .5);
958 g_signal_connect_swapped (G_OBJECT (state->add_button), "clicked",
959 G_CALLBACK (cb_dialog_add_clicked), state);
961 state->change_button = go_gtk_builder_get_widget (state->gui,
962 "changebutton");
963 g_signal_connect (G_OBJECT (state->change_button), "clicked",
964 G_CALLBACK (cb_dialog_change_clicked), state);
966 state->delete_button = go_gtk_builder_get_widget (state->gui,
967 "deletebutton");
968 gtk_button_set_alignment (GTK_BUTTON (state->delete_button), 0.5, .5);
969 g_signal_connect (G_OBJECT (state->delete_button), "clicked",
970 G_CALLBACK (cb_dialog_delete_clicked), state);
972 state->stop_button = go_gtk_builder_get_widget (state->gui, "stopbutton");
973 g_signal_connect_swapped (G_OBJECT (state->stop_button),
974 "clicked", G_CALLBACK (cb_stop_solver),
975 state);
977 /* target_entry */
978 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui,
979 "parameter-grid"));
980 state->target_entry = gnm_expr_entry_new (state->wbcg, TRUE);
981 gnm_expr_entry_set_flags (state->target_entry,
982 GNM_EE_SINGLE_RANGE |
983 GNM_EE_FORCE_ABS_REF |
984 GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
985 gtk_widget_set_hexpand (GTK_WIDGET (state->target_entry), TRUE);
986 gtk_grid_attach (grid, GTK_WIDGET (state->target_entry), 1, 0, 2, 1);
987 gnm_editable_enters (GTK_WINDOW (state->dialog),
988 GTK_WIDGET (state->target_entry));
989 gtk_widget_show (GTK_WIDGET (state->target_entry));
990 g_signal_connect_after (G_OBJECT (state->target_entry), "changed",
991 G_CALLBACK (dialog_set_main_button_sensitivity),
992 state);
994 /* change_cell_entry */
995 state->change_cell_entry = gnm_expr_entry_new (state->wbcg, TRUE);
996 gnm_expr_entry_set_flags (state->change_cell_entry,
997 GNM_EE_SINGLE_RANGE |
998 GNM_EE_FORCE_ABS_REF |
999 GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
1000 gtk_widget_set_hexpand (GTK_WIDGET (state->change_cell_entry), TRUE);
1001 gtk_grid_attach (grid,
1002 GTK_WIDGET (state->change_cell_entry), 1, 2, 2, 1);
1003 gnm_editable_enters (GTK_WINDOW (state->dialog),
1004 GTK_WIDGET (state->change_cell_entry));
1005 gtk_widget_show (GTK_WIDGET (state->change_cell_entry));
1006 g_signal_connect_after (G_OBJECT (state->change_cell_entry), "changed",
1007 G_CALLBACK (dialog_set_main_button_sensitivity), state);
1009 /* Algorithm */
1010 state->algorithm_combo = GTK_COMBO_BOX
1011 (go_gtk_builder_get_widget (state->gui, "algorithm_combo"));
1012 renderer = (GtkCellRenderer*) gtk_cell_renderer_text_new();
1013 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (state->algorithm_combo), renderer, TRUE);
1014 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (state->algorithm_combo), renderer,
1015 "text", 0,
1016 NULL);
1017 fill_algorithm_combo (state, param->options.model_type);
1019 for (i = 0; model_type_group[i]; i++) {
1020 const char *bname = model_type_group[i];
1021 GtkWidget *w = go_gtk_builder_get_widget(state->gui, bname);
1022 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w),
1023 param->options.model_type ==
1024 (GnmSolverModelType)i);
1025 g_signal_connect (G_OBJECT (w), "clicked",
1026 G_CALLBACK (cb_dialog_model_type_clicked), state);
1029 /* Options */
1030 state->max_iter_entry = go_gtk_builder_get_widget (state->gui,
1031 "max_iter_entry");
1032 gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->max_iter_entry),
1033 param->options.max_iter);
1035 state->max_time_entry = go_gtk_builder_get_widget (state->gui,
1036 "max_time_entry");
1037 gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->max_time_entry),
1038 param->options.max_time_sec);
1040 state->gradient_order_entry = go_gtk_builder_get_widget (state->gui,
1041 "gradient_order_entry");
1042 gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->gradient_order_entry),
1043 param->options.gradient_order);
1045 /* lhs_entry */
1046 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui,
1047 "constraints-grid"));
1048 state->lhs.entry = gnm_expr_entry_new (state->wbcg, TRUE);
1049 gnm_expr_entry_set_flags (state->lhs.entry,
1050 GNM_EE_SINGLE_RANGE |
1051 GNM_EE_FORCE_ABS_REF |
1052 GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
1053 gtk_widget_set_hexpand (GTK_WIDGET (state->lhs.entry), TRUE);
1054 gtk_grid_attach (grid, GTK_WIDGET (state->lhs.entry), 0, 4, 1, 1);
1055 state->lhs.label = go_gtk_builder_get_widget (state->gui, "lhs_label");
1056 gtk_label_set_mnemonic_widget (GTK_LABEL (state->lhs.label),
1057 GTK_WIDGET (state->lhs.entry));
1058 gtk_widget_show (GTK_WIDGET (state->lhs.entry));
1059 g_signal_connect_after (G_OBJECT (state->lhs.entry),
1060 "changed",
1061 G_CALLBACK (dialog_set_sec_button_sensitivity), state);
1062 g_signal_connect_swapped (
1063 gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (state->lhs.entry)),
1064 "activate", G_CALLBACK (cb_dialog_add_clicked), state);
1066 /* rhs_entry */
1067 state->rhs.entry = gnm_expr_entry_new (state->wbcg, TRUE);
1068 gnm_expr_entry_set_flags (state->rhs.entry,
1069 GNM_EE_SINGLE_RANGE |
1070 GNM_EE_FORCE_ABS_REF |
1071 GNM_EE_SHEET_OPTIONAL |
1072 GNM_EE_CONSTANT_ALLOWED,
1073 GNM_EE_MASK);
1074 gtk_widget_set_hexpand (GTK_WIDGET (state->rhs.entry), TRUE);
1075 gtk_grid_attach (grid, GTK_WIDGET (state->rhs.entry), 2, 4, 1, 1);
1076 gtk_widget_show (GTK_WIDGET (state->rhs.entry));
1077 state->rhs.label = go_gtk_builder_get_widget (state->gui, "rhs_label");
1078 gtk_label_set_mnemonic_widget (
1079 GTK_LABEL (state->rhs.label), GTK_WIDGET (state->rhs.entry));
1080 g_signal_connect_after (G_OBJECT (state->rhs.entry),
1081 "changed",
1082 G_CALLBACK (dialog_set_sec_button_sensitivity), state);
1083 g_signal_connect_swapped (
1084 gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (state->rhs.entry)),
1085 "activate", G_CALLBACK (cb_dialog_add_clicked), state);
1087 /* type_menu */
1088 state->type_combo = GTK_COMBO_BOX
1089 (go_gtk_builder_get_widget (state->gui, "type_menu"));
1090 gtk_combo_box_set_active (state->type_combo, 0);
1091 g_signal_connect (state->type_combo, "changed",
1092 G_CALLBACK (dialog_set_sec_button_sensitivity),
1093 state);
1095 /* constraint_list */
1096 state->constraint_list = GTK_TREE_VIEW (go_gtk_builder_get_widget
1097 (state->gui, "constraint_list"));
1099 state->constr = NULL;
1100 g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (state->constraint_list)), "changed",
1101 G_CALLBACK (constraint_select_click), state);
1102 gtk_tree_view_set_reorderable (state->constraint_list, TRUE);
1103 store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
1104 gtk_tree_view_set_model (state->constraint_list, GTK_TREE_MODEL(store));
1105 renderer = gtk_cell_renderer_text_new ();
1106 column = gtk_tree_view_column_new_with_attributes (
1107 _("Subject to the Constraints:"),
1108 renderer, "text", 0, NULL);
1109 gtk_tree_view_column_set_expand (column, TRUE);
1110 gtk_tree_view_append_column (state->constraint_list, column);
1113 GtkWidget *w = GTK_WIDGET (state->constraint_list);
1114 int width, height, vsep;
1115 PangoLayout *layout =
1116 gtk_widget_create_pango_layout (w, "Mg19");
1118 gtk_widget_style_get (w,
1119 "vertical_separator", &vsep,
1120 NULL);
1122 pango_layout_get_pixel_size (layout, &width, &height);
1123 gtk_widget_set_size_request (w,
1125 (2 * height + vsep) * (4 + 1));
1126 g_object_unref (layout);
1129 /* Loading the old solver specs... from param */
1131 for (cl = param->constraints; cl; cl = cl->next) {
1132 GnmSolverConstraint const *c = cl->data;
1133 GtkTreeIter iter;
1134 char *str;
1136 gtk_list_store_append (store, &iter);
1137 str = gnm_solver_constraint_as_str (c, state->sheet);
1138 gtk_list_store_set (store, &iter, 0, str, 1, c, -1);
1139 g_free (str);
1141 g_object_unref (store);
1143 INIT_BOOL_ENTRY ("autoscale_button", options.automatic_scaling);
1144 INIT_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
1145 INIT_BOOL_ENTRY ("all_int_button", options.assume_discrete);
1146 INIT_BOOL_ENTRY ("program", options.program_report);
1147 INIT_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
1149 input = gnm_solver_param_get_input (param);
1150 if (input != NULL)
1151 gnm_expr_entry_load_from_text (state->change_cell_entry,
1152 value_peek_string (input));
1153 target_cell = gnm_solver_param_get_target_cell (param);
1154 if (target_cell)
1155 gnm_expr_entry_load_from_text (state->target_entry,
1156 cell_name (target_cell));
1157 else {
1158 SheetView *sv = wb_control_cur_sheet_view
1159 (GNM_WBC (state->wbcg));
1160 if (sv) {
1161 GnmRange first = {sv->edit_pos, sv->edit_pos};
1162 gnm_expr_entry_load_from_range (state->target_entry,
1163 state->sheet, &first);
1167 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1168 go_gtk_builder_get_widget(state->gui, "max_button")),
1169 param->problem_type == GNM_SOLVER_MAXIMIZE);
1170 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1171 go_gtk_builder_get_widget(state->gui, "min_button")),
1172 param->problem_type == GNM_SOLVER_MINIMIZE);
1173 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1174 go_gtk_builder_get_widget(state->gui, "no_scenario")),
1175 ! param->options.add_scenario);
1176 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1177 go_gtk_builder_get_widget(state->gui, "optimal_scenario")),
1178 param->options.add_scenario);
1180 state->scenario_name_entry = go_gtk_builder_get_widget
1181 (state->gui, "scenario_name_entry");
1182 gtk_entry_set_text (GTK_ENTRY (state->scenario_name_entry),
1183 param->options.scenario_name);
1185 state->run.status_widget = go_gtk_builder_get_widget (state->gui, "solver_status_label");
1186 state->run.problem_status_widget = go_gtk_builder_get_widget (state->gui, "problem_status_label");
1187 state->run.objective_value_widget = go_gtk_builder_get_widget (state->gui, "objective_value_label");
1188 state->run.timer_widget = go_gtk_builder_get_widget (state->gui, "elapsed_time_label");
1189 state->run.spinner = go_gtk_builder_get_widget (state->gui, "run_spinner");
1192 /* Done */
1193 gnm_expr_entry_grab_focus (state->target_entry, FALSE);
1194 wbcg_set_entry (state->wbcg, state->target_entry);
1196 dialog_set_main_button_sensitivity (NULL, state);
1197 dialog_set_sec_button_sensitivity (NULL, state);
1199 /* dialog */
1200 wbc_gtk_attach_guru (state->wbcg, state->dialog);
1202 g_signal_connect_swapped (G_OBJECT (state->dialog),
1203 "destroy",
1204 G_CALLBACK (cb_dialog_solver_destroy),
1205 state);
1206 g_object_set_data_full (G_OBJECT (state->dialog),
1207 "state", state,
1208 (GDestroyNotify)unref_state);
1210 return FALSE;
1214 * dialog_solver:
1215 * @wbcg:
1216 * @sheet:
1218 * Create the dialog (guru).
1221 void
1222 dialog_solver (WBCGtk *wbcg, Sheet *sheet)
1224 SolverState *state;
1225 GnmSolverParameters *old_params = sheet->solver_parameters;
1226 gboolean got_it;
1227 int pass;
1229 /* Only pop up one copy per workbook */
1230 if (gnm_dialog_raise_if_exists (wbcg, SOLVER_KEY))
1231 return;
1234 * First time around, pick a functional algorithm preferably one we
1235 * can determine is functional without asking the user anything.
1237 got_it = gnm_solver_factory_functional (old_params->options.algorithm,
1238 NULL);
1239 for (pass = 1; !got_it && pass <= 2; pass++) {
1240 GSList *l;
1241 WBCGtk *wbcg2 = pass == 2 ? wbcg : NULL;
1243 for (l = gnm_solver_db_get (); l; l = l->next) {
1244 GnmSolverFactory *factory = l->data;
1245 if (old_params->options.model_type != factory->type)
1246 continue;
1247 if (gnm_solver_factory_functional (factory, wbcg2)) {
1248 got_it = TRUE;
1249 gnm_solver_param_set_algorithm (old_params,
1250 factory);
1251 break;
1256 state = g_new0 (SolverState, 1);
1257 state->ref_count = 1;
1258 state->wbcg = wbcg;
1259 state->sheet = sheet;
1260 state->warning_dialog = NULL;
1261 state->orig_params = gnm_solver_param_dup (sheet->solver_parameters,
1262 sheet);
1264 if (dialog_solver_init (state)) {
1265 go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
1266 _("Could not create the Solver dialog."));
1267 unref_state (state);
1268 return;
1271 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog), wbcg,
1272 GNM_DIALOG_DESTROY_SHEET_REMOVED);
1274 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1275 SOLVER_KEY);
1277 gtk_widget_show (state->dialog);