1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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>
33 #include <dialogs/tool-dialogs.h>
37 #include <sheet-view.h>
41 #include <parse-util.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>
51 #include <goffice/goffice.h>
53 #include <scenarios.h>
55 #define SOLVER_KEY "solver-dialog"
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
;
79 GtkComboBox
*type_combo
;
80 GtkComboBox
*algorithm_combo
;
81 GtkTreeView
*constraint_list
;
82 GnmSolverConstraint
*constr
;
83 GtkWidget
*warning_dialog
;
87 GtkWidget
*timer_widget
;
89 GtkWidget
*status_widget
;
90 GtkWidget
*problem_status_widget
;
91 GtkWidget
*objective_value_widget
;
100 GnmSolverParameters
*orig_params
;
104 static char const * const problem_type_group
[] = {
111 static char const * const model_type_group
[] = {
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
)
137 dialog_set_sec_button_sensitivity (G_GNUC_UNUSED GtkWidget
*dummy
,
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. */
161 constraint_select_click (GtkTreeSelection
*Selection
,
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);
172 state
->constr
= NULL
;
173 dialog_set_sec_button_sensitivity (NULL
, state
);
175 if (state
->constr
== NULL
)
179 lhs
= gnm_solver_constraint_get_lhs (c
);
181 GnmExprTop
const *texpr
=
182 gnm_expr_top_new_constant (value_dup (lhs
));
185 gnm_expr_entry_load_from_expr
188 parse_pos_init_sheet (&pp
, state
->sheet
));
189 gnm_expr_top_unref (texpr
);
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
));
199 gnm_expr_entry_load_from_expr
202 parse_pos_init_sheet (&pp
, state
->sheet
));
203 gnm_expr_top_unref (texpr
);
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:
218 cb_dialog_delete_clicked (G_GNUC_UNUSED GtkWidget
*button
, SolverState
*state
)
220 if (state
->constr
!= NULL
) {
223 GnmSolverParameters
*param
= state
->sheet
->solver_parameters
;
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
),
233 gtk_list_store_remove ((GtkListStore
*)store
, &iter
);
238 constraint_fill_row (SolverState
*state
, GtkListStore
*store
, GtkTreeIter
*iter
)
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);
248 gtk_tree_selection_select_iter (gtk_tree_view_get_selection (state
->constraint_list
), iter
);
252 cb_dialog_add_clicked (SolverState
*state
)
254 if (dialog_set_sec_button_sensitivity (NULL
, state
)) {
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
);
263 g_slist_append (param
->constraints
, state
->constr
);
268 cb_dialog_change_clicked (GtkWidget
*button
, SolverState
*state
)
270 if (state
->constr
!= NULL
) {
274 if (gtk_tree_selection_get_selected (
275 gtk_tree_view_get_selection (state
->constraint_list
),
277 constraint_fill_row (state
, (GtkListStore
*)store
, &iter
);
282 dialog_set_main_button_sensitivity (G_GNUC_UNUSED GtkWidget
*dummy
,
287 ready
= gnm_expr_entry_is_cell_ref (state
->target_entry
, state
->sheet
,
289 && gnm_expr_entry_is_cell_ref (state
->change_cell_entry
,
291 gtk_widget_set_sensitive (state
->solve_button
, ready
);
295 fill_algorithm_combo (SolverState
*state
, GnmSolverModelType type
)
297 GtkListStore
*store
=
298 gtk_list_store_new (2, G_TYPE_STRING
, G_TYPE_POINTER
);
301 GnmSolverParameters
*param
=state
->sheet
->solver_parameters
;
303 gtk_combo_box_set_model (state
->algorithm_combo
, GTK_TREE_MODEL (store
));
306 for (solvers
= gnm_solver_db_get (); solvers
; solvers
= solvers
->next
) {
307 GnmSolverFactory
*entry
= solvers
->data
;
308 if (type
!= entry
->type
)
310 l
= g_slist_prepend (l
, entry
);
312 solvers
= g_slist_reverse (l
);
314 gtk_widget_set_sensitive (GTK_WIDGET (state
->solve_button
),
319 for (l
= solvers
, i
= 0; l
; l
= l
->next
, i
++) {
320 GnmSolverFactory
*factory
= l
->data
;
323 if (param
->options
.algorithm
== factory
)
326 gtk_list_store_append (store
, &iter
);
327 gtk_list_store_set (store
, &iter
,
332 g_slist_free (solvers
);
334 gtk_combo_box_set_active (state
->algorithm_combo
, sel
);
336 g_object_unref (store
);
342 cb_dialog_model_type_clicked (G_GNUC_UNUSED GtkWidget
*button
,
345 GnmSolverModelType type
;
348 type
= gnm_gui_group_value (state
->gui
, model_type_group
);
349 any
= fill_algorithm_combo (state
, type
);
352 go_gtk_notice_nonmodal_dialog
353 (GTK_WINDOW (state
->dialog
),
354 &(state
->warning_dialog
),
356 _("Looking for a subject for your thesis? "
357 "Maybe you would like to write a solver for "
363 unref_state (SolverState
*state
)
366 if (state
->ref_count
> 0)
369 if (state
->orig_params
)
370 g_object_unref (state
->orig_params
);
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_) \
385 GtkWidget *w_ = go_gtk_builder_get_widget (state->gui, (name_)); \
386 param->field_ = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w_)); \
390 extract_settings (SolverState
*state
)
392 GnmSolverParameters
*param
= state
->sheet
->solver_parameters
;
394 GnmValue
*target_range
;
395 GnmValue
*input_range
;
396 GnmSolverFactory
*factory
= NULL
;
398 target_range
= gnm_expr_entry_parse_as_value (state
->target_entry
,
400 input_range
= gnm_expr_entry_parse_as_value (state
->change_cell_entry
,
403 gnm_solver_param_set_input (param
, input_range
);
405 gnm_solver_param_set_target (param
,
407 ? &target_range
->v_range
.cell
.a
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
);
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
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"),
459 g_object_unref (state
->orig_params
);
461 gnm_solver_param_dup (sheet
->solver_parameters
,
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
);
485 wbcg_edit_finish (state
->wbcg
, WBC_EDIT_REJECT
, NULL
);
487 state
->dialog
= NULL
;
491 cb_dialog_close_clicked (G_GNUC_UNUSED GtkWidget
*button
,
494 gtk_widget_destroy (state
->dialog
);
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
);
506 g_warning ("Failed to stop solver!");
508 g_object_set (sol
, "result", NULL
, NULL
);
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;
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;
536 update_obj_value (SolverState
*state
)
538 GnmSolver
*sol
= state
->run
.solver
;
539 GnmSolverResult
*r
= sol
->result
;
543 switch (r
? r
->quality
: GNM_SOLVER_RESULT_NONE
) {
545 case GNM_SOLVER_RESULT_NONE
:
549 case GNM_SOLVER_RESULT_FEASIBLE
:
553 case GNM_SOLVER_RESULT_OPTIMAL
:
557 case GNM_SOLVER_RESULT_INFEASIBLE
:
558 txt
= _("Infeasible");
561 case GNM_SOLVER_RESULT_UNBOUNDED
:
562 txt
= _("Unbounded");
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 (),
575 gtk_label_set_text (GTK_LABEL (state
->run
.objective_value_widget
),
579 remove_objective_value_source (state
);
583 cb_notify_status (SolverState
*state
)
585 GnmSolver
*sol
= state
->run
.solver
;
587 gboolean finished
= gnm_solver_finished (sol
);
588 gboolean running
= FALSE
;
590 switch (sol
->status
) {
591 case GNM_SOLVER_STATUS_READY
:
594 case GNM_SOLVER_STATUS_PREPARING
:
595 text
= _("Preparing");
597 case GNM_SOLVER_STATUS_PREPARED
:
598 text
= _("Prepared");
600 case GNM_SOLVER_STATUS_RUNNING
:
604 case GNM_SOLVER_STATUS_DONE
:
608 case GNM_SOLVER_STATUS_ERROR
:
611 case GNM_SOLVER_STATUS_CANCELLED
:
612 text
= _("Cancelled");
617 char *text2
= g_strconcat (text
,
618 " (", sol
->reason
, ")",
620 gtk_label_set_text (GTK_LABEL (state
->run
.status_widget
),
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
);
636 remove_timer_source (state
);
637 if (state
->run
.in_main
)
643 cb_obj_val_tick (SolverState
*state
)
645 state
->run
.obj_val_source
= 0;
646 update_obj_value (state
);
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
);
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;
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
);
674 if (gnm_solver_check_timeout (sol
)) {
675 cb_notify_status (state
);
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
);
691 static GnmSolverResult
*
692 run_solver (SolverState
*state
, GnmSolverParameters
*param
)
698 GnmSolver
*sol
= NULL
;
699 GnmValue
const *vinput
;
700 GtkWindow
*top
= GTK_WINDOW (gtk_widget_get_toplevel (state
->dialog
));
701 GnmSolverResult
*res
= NULL
;
705 sol
= gnm_solver_factory_functional (param
->options
.algorithm
,
707 ? gnm_solver_factory_create (param
->options
.algorithm
, param
)
710 go_gtk_notice_dialog (top
, GTK_MESSAGE_ERROR
,
711 _("The chosen solver is not functional."));
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
),
726 G_CALLBACK (cb_notify_status
),
728 g_signal_connect_swapped (G_OBJECT (sol
),
730 G_CALLBACK (cb_notify_status
),
732 cb_notify_status (state
);
734 g_signal_connect_swapped (G_OBJECT (sol
),
736 G_CALLBACK (cb_notify_result
),
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
),
750 state
->run
.in_main
++;
751 go_cmd_context_set_sensitive (GO_CMD_CONTEXT (state
->wbcg
), FALSE
);
753 go_cmd_context_set_sensitive (GO_CMD_CONTEXT (state
->wbcg
), TRUE
);
754 state
->run
.in_main
--;
755 ok
= gnm_solver_has_solution (sol
);
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 /* ---------------------------------------- */
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
779 workbook_sheet_state_new (wb
),
780 (GOUndoBinaryFunc
)workbook_sheet_state_restore
,
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
789 workbook_sheet_state_new (wb
),
790 (GOUndoBinaryFunc
)workbook_sheet_state_restore
,
792 (GFreeFunc
)workbook_sheet_state_free
);
793 redo
= go_undo_combine (redo
, redo_report
);
796 cmd_generic (GNM_WBC (state
->wbcg
),
799 res
= g_object_ref (sol
->result
);
805 g_object_unref (undo
);
807 if (state
->run
.solver
) {
808 g_object_unref (state
->run
.solver
);
809 state
->run
.solver
= NULL
;
819 solver_add_scenario (SolverState
*state
, GnmSolverResult
*res
, gchar
const *name
)
821 GnmSolverParameters
*param
= state
->sheet
->solver_parameters
;
822 GnmValue
const *vinput
;
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"));
836 case GNM_SOLVER_RESULT_FEASIBLE
:
837 gnm_scenario_set_comment
838 (sc
, _("Feasible solution created by solver.\n"));
843 gnm_scenario_add_area (sc
, &sr
);
845 cmd_scenario_add (wbc
, sc
, sc
->sheet
);
849 * cb_dialog_solve_clicked:
856 cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget
*button
,
859 GnmSolverResult
*res
;
860 GnmSolverParameters
*param
= state
->sheet
->solver_parameters
;
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
,
877 check_for_changed_options (state
);
879 res
= run_solver (state
, param
);
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
);
892 go_gtk_notice_nonmodal_dialog
893 (GTK_WINDOW (state
->dialog
),
894 &(state
->warning_dialog
),
904 #define INIT_BOOL_ENTRY(name_, field_) \
906 GtkWidget *w_ = go_gtk_builder_get_widget (state->gui, (name_)); \
907 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w_), \
913 * dialog_solver_init:
916 * Create the dialog (guru).
920 dialog_solver_init (SolverState
*state
)
923 GnmSolverParameters
*param
;
924 GtkCellRenderer
*renderer
;
926 GtkTreeViewColumn
*column
;
928 GnmCell
*target_cell
;
929 GnmValue
const *input
;
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
)
938 state
->dialog
= go_gtk_builder_get_widget (state
->gui
, "Solver");
939 if (state
->dialog
== NULL
)
942 state
->notebook
= go_gtk_builder_get_widget (state
->gui
, "solver_notebook");
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
,
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
,
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
),
978 grid
= GTK_GRID (go_gtk_builder_get_widget (state
->gui
,
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
),
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
);
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
,
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
);
1030 state
->max_iter_entry
= go_gtk_builder_get_widget (state
->gui
,
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
,
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
);
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
),
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
);
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
,
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
),
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
);
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
),
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
,
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
;
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);
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
);
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
);
1155 gnm_expr_entry_load_from_text (state
->target_entry
,
1156 cell_name (target_cell
));
1158 SheetView
*sv
= wb_control_cur_sheet_view
1159 (GNM_WBC (state
->wbcg
));
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");
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
);
1200 wbc_gtk_attach_guru (state
->wbcg
, state
->dialog
);
1202 g_signal_connect_swapped (G_OBJECT (state
->dialog
),
1204 G_CALLBACK (cb_dialog_solver_destroy
),
1206 g_object_set_data_full (G_OBJECT (state
->dialog
),
1208 (GDestroyNotify
)unref_state
);
1218 * Create the dialog (guru).
1222 dialog_solver (WBCGtk
*wbcg
, Sheet
*sheet
)
1225 GnmSolverParameters
*old_params
= sheet
->solver_parameters
;
1229 /* Only pop up one copy per workbook */
1230 if (gnm_dialog_raise_if_exists (wbcg
, SOLVER_KEY
))
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
,
1239 for (pass
= 1; !got_it
&& pass
<= 2; pass
++) {
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
)
1247 if (gnm_solver_factory_functional (factory
, wbcg2
)) {
1249 gnm_solver_param_set_algorithm (old_params
,
1256 state
= g_new0 (SolverState
, 1);
1257 state
->ref_count
= 1;
1259 state
->sheet
= sheet
;
1260 state
->warning_dialog
= NULL
;
1261 state
->orig_params
= gnm_solver_param_dup (sheet
->solver_parameters
,
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
);
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
),
1277 gtk_widget_show (state
->dialog
);