dialog-simulation.c 12.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * dialog-simulation.c:
 *
 * Authors:
 *        Jukka-Pekka Iivonen <jiivonen@hutcs.cs.hut.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
19 20
 **/
#include <gnumeric-config.h>
21
#include <glib/gi18n-lib.h>
22
#include <gnumeric.h>
23 24
#include <dialogs/dialogs.h>
#include <dialogs/help.h>
25 26 27 28 29

#include <sheet.h>
#include <cell.h>
#include <ranges.h>
#include <gui-util.h>
30 31
#include <dialogs/tool-dialogs.h>
#include <dialogs/dao-gui-utils.h>
32
#include <value.h>
33
#include <wbc-gtk.h>
34

Morten Welinder's avatar
Morten Welinder committed
35
#include <widgets/gnm-expr-entry.h>
36
#include <widgets/gnm-dao.h>
37
#include <tools/simulation.h>
38

Jody Goldberg's avatar
Jody Goldberg committed
39
#include <string.h>
40 41 42

#define SIMULATION_KEY         "simulation-dialog"

43
typedef GnmGenericToolState SimulationState;
44

45 46 47
static GtkTextBuffer   *results_buffer;
static int             results_sim_index;
static simulation_t    *current_sim;
48

49 50 51 52 53 54 55 56
/**
 * simulation_update_sensitivity_cb:
 * @dummy:
 * @state:
 *
 * Update the dialog widgets sensitivity
 **/
static void
Morten Welinder's avatar
Morten Welinder committed
57 58
simulation_update_sensitivity_cb (G_GNUC_UNUSED GtkWidget *dummy,
				  SimulationState *state)
59
{
Jody Goldberg's avatar
Jody Goldberg committed
60 61
        GnmValue *input_range  = NULL;
        GnmValue *output_vars  = NULL;
62 63

        input_range = gnm_expr_entry_parse_as_value (
64
		GNM_EXPR_ENTRY (state->input_entry), state->sheet);
65 66
	if (input_range == NULL) {
		gtk_label_set_text (GTK_LABEL (state->warning),
67
				    _("The input variable range is invalid."));
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
		gtk_widget_set_sensitive (state->ok_button, FALSE);
		return;
	} else
		value_release (input_range);

	output_vars =  gnm_expr_entry_parse_as_value
		(state->input_entry_2, state->sheet);
	if (output_vars == NULL) {
		gtk_label_set_text (GTK_LABEL (state->warning),
				    _("The output variable range is invalid."));
		gtk_widget_set_sensitive (state->ok_button, FALSE);
		return;
	} else
		value_release (output_vars);

83
	if (!gnm_dao_is_ready (GNM_DAO (state->gdao))) {
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
			gtk_label_set_text (GTK_LABEL (state->warning),
					    _("The output range is invalid."));
			gtk_widget_set_sensitive (state->ok_button, FALSE);
			return;
	}

	gtk_label_set_text (GTK_LABEL (state->warning), "");
	gtk_widget_set_sensitive (state->ok_button, TRUE);
	return;
}

static gboolean
prepare_ranges (simulation_t *sim)
{
	int i, n, base_col, base_row;

100 101
	if (!VALUE_IS_CELLRANGE (sim->inputs) ||
	    !VALUE_IS_CELLRANGE (sim->outputs))
102 103
		return TRUE;

104 105
	sim->ref_inputs  = gnm_rangeref_dup (value_get_rangeref (sim->inputs));
	sim->ref_outputs = gnm_rangeref_dup (value_get_rangeref (sim->outputs));
106 107 108 109 110 111 112

	sim->n_input_vars =
		(abs (sim->ref_inputs->a.col - sim->ref_inputs->b.col) + 1) *
		(abs (sim->ref_inputs->a.row - sim->ref_inputs->b.row) + 1);
	sim->n_output_vars =
		(abs (sim->ref_outputs->a.col - sim->ref_outputs->b.col) + 1) *
		(abs (sim->ref_outputs->a.row - sim->ref_outputs->b.row) + 1);
113
	sim->n_vars = sim->n_input_vars + sim->n_output_vars;
114 115 116 117 118 119 120 121 122 123

	/* Get the intput cells into a list. */
	sim->list_inputs = NULL;
	base_col = MIN (sim->ref_inputs->a.col, sim->ref_inputs->b.col);
	base_row = MIN (sim->ref_inputs->a.row, sim->ref_inputs->b.row);
	for (i  = base_col;
	     i <= MAX (sim->ref_inputs->a.col, sim->ref_inputs->b.col); i++) {
		for (n = base_row;
		     n<= MAX (sim->ref_inputs->a.row, sim->ref_inputs->b.row);
		     n++) {
124 125
			GnmCell *cell = sheet_cell_fetch (sim->ref_inputs->a.sheet, i, n);
			sim->list_inputs = g_slist_append (sim->list_inputs, cell);
126 127 128 129 130 131 132 133 134 135 136 137
		}
	}

	/* Get the output cells into a list. */
	sim->list_outputs = NULL;
	base_col = MIN (sim->ref_outputs->a.col, sim->ref_outputs->b.col);
	base_row = MIN (sim->ref_outputs->a.row, sim->ref_outputs->b.row);
	for (i  = base_col;
	     i <= MAX (sim->ref_outputs->a.col, sim->ref_outputs->b.col); i++) {
		for (n = base_row;
		     n<= MAX (sim->ref_outputs->a.row, sim->ref_outputs->b.row);
		     n++) {
138 139
			GnmCell *cell = sheet_cell_fetch (sim->ref_outputs->a.sheet, i, n);
			sim->list_outputs = g_slist_append (sim->list_outputs, cell);
140 141 142 143 144 145
		}
	}

	return FALSE;
}

146
static void
147
update_log (SimulationState *state, simulation_t *sim)
148
{
149
	char const *txt [6] = {
150 151 152 153 154 155 156 157 158 159
		_("Simulations"), _("Iterations"), _("# Input variables"),
		_("# Output variables"), _("Runtime"), _("Run on")
	};
	GtkTreeIter  iter;
	GtkListStore *store;
	GtkTreePath  *path;
	GtkWidget    *view;
	GString      *buf;
	int          i;

Morten Welinder's avatar
Morten Welinder committed
160
	view = go_gtk_builder_get_widget (state->gui, "last-run-view");
161 162 163 164 165 166 167 168

	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);

	for (i = 0; i < 6; i++) {
		buf = g_string_new (NULL);
		switch (i) {
		case 0:
			g_string_append_printf (buf, "%d",
Morten Welinder's avatar
Morten Welinder committed
169
						sim->last_round -
170 171 172 173 174 175 176 177 178 179 180 181
						sim->first_round + 1);
			break;
		case 1:
			g_string_append_printf (buf, "%d", sim->n_iterations);
			break;
		case 2:
			g_string_append_printf (buf, "%d", sim->n_input_vars);
			break;
		case 3:
			g_string_append_printf (buf, "%d", sim->n_output_vars);
			break;
		case 4:
182 183 184 185 186 187
			g_string_append_printf (buf, "%.2" GNM_FORMAT_g,
						sim->end.tv_sec -
						sim->start.tv_sec +
						(sim->end.tv_usec -
						 sim->start.tv_usec) /
						(gnm_float) G_USEC_PER_SEC);
188 189 190 191 192 193 194 195
			break;
		case 5:
			dao_append_date (buf);
			break;
		default:
			g_string_append_printf (buf, "Error");
			break;
		}
196

197 198 199 200 201 202
		gtk_list_store_append (store, &iter);
		gtk_list_store_set (store, &iter, 0, txt [i], 1, buf->str, -1);
		g_string_free (buf, FALSE);
	}

	path = gtk_tree_path_new_from_string ("0");
203
	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) {
204
		;		/* Do something */
205 206 207
	} else {
		g_warning ("Did not get a valid iterator");
	}
208
	gtk_tree_path_free (path);
Morten Welinder's avatar
Morten Welinder committed
209

210 211
	gtk_tree_view_append_column
		(GTK_TREE_VIEW (view),
Morten Welinder's avatar
Morten Welinder committed
212
		 gtk_tree_view_column_new_with_attributes
213 214 215 216
		 (_("Name"),
		  gtk_cell_renderer_text_new (), "text", 0, NULL));
	gtk_tree_view_append_column
		(GTK_TREE_VIEW (view),
Morten Welinder's avatar
Morten Welinder committed
217
		 gtk_tree_view_column_new_with_attributes
218 219 220
		 (_("Value"),
		  gtk_cell_renderer_text_new (), "text", 1, NULL));
	gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (store));
Morten Welinder's avatar
Morten Welinder committed
221
	g_object_unref (store);
222 223
}

224 225 226 227 228 229
static void
update_results_view (simulation_t *sim)
{
	GString *buf;
	int     i;

230
	buf = g_string_new (NULL);
231

232 233
	g_string_append_printf (buf, "Simulation #%d\n\n", results_sim_index + 1);
	g_string_append_printf (buf, "%-20s %10s %10s %10s\n", _("Variable"),
234 235
			   _("Min"), _("Average"), _("Max"));
	for (i = 0; i < sim->n_vars; i++)
236 237
		g_string_append_printf (buf, "%-20s %10" GNM_FORMAT_g " %10"
					GNM_FORMAT_G " %10" GNM_FORMAT_g "\n",
238
				   sim->cellnames [i],
239 240 241
				   sim->stats [results_sim_index]->min [i],
				   sim->stats [results_sim_index]->mean [i],
				   sim->stats [results_sim_index]->max [i]);
242 243 244 245 246 247

	gtk_text_buffer_set_text (results_buffer, buf->str, strlen (buf->str));
	g_string_free (buf, FALSE);
}

static void
Morten Welinder's avatar
Morten Welinder committed
248
prev_button_cb (G_GNUC_UNUSED GtkWidget *button,
249
		SimulationState *state)
250 251 252
{
	GtkWidget *w;

253 254
	if (results_sim_index > current_sim->first_round)
		--results_sim_index;
255 256

	if (results_sim_index == current_sim->first_round) {
Morten Welinder's avatar
Morten Welinder committed
257
		w = go_gtk_builder_get_widget (state->gui, "prev-button");
258 259 260
		gtk_widget_set_sensitive (w, FALSE);
	}

Morten Welinder's avatar
Morten Welinder committed
261
	w = go_gtk_builder_get_widget (state->gui, "next-button");
262 263 264 265 266
	gtk_widget_set_sensitive (w, TRUE);
	update_results_view (current_sim);
}

static void
Morten Welinder's avatar
Morten Welinder committed
267
next_button_cb (G_GNUC_UNUSED GtkWidget *button,
268
		SimulationState *state)
269 270 271
{
	GtkWidget *w;

272 273
	if (results_sim_index < current_sim->last_round)
		++results_sim_index;
274 275

	if (results_sim_index == current_sim->last_round) {
Morten Welinder's avatar
Morten Welinder committed
276
		w = go_gtk_builder_get_widget (state->gui, "next-button");
277 278 279
		gtk_widget_set_sensitive (w, FALSE);
	}

Morten Welinder's avatar
Morten Welinder committed
280
	w = go_gtk_builder_get_widget (state->gui, "prev-button");
281 282 283 284
	gtk_widget_set_sensitive (w, TRUE);
	update_results_view (current_sim);
}

285 286 287 288 289 290 291 292 293 294
/**
 * simulation_ok_clicked_cb:
 * @button:
 * @state:
 *
 * Retrieve the information from the dialog and call the advanced_filter.
 * Note that we assume that the ok_button is only active if the entry fields
 * contain sensible data.
 **/
static void
Morten Welinder's avatar
Morten Welinder committed
295
simulation_ok_clicked_cb (G_GNUC_UNUSED GtkWidget *button,
296
			  SimulationState *state)
297 298 299
{
	data_analysis_output_t  dao;
	GtkWidget               *w;
300
	gchar const		*err;
301
	static simulation_t     sim;
302

303 304
	simulation_tool_destroy (current_sim);

305
	sim.inputs = gnm_expr_entry_parse_as_value
306
		(GNM_EXPR_ENTRY (state->input_entry), state->sheet);
307 308 309 310

	sim.outputs = gnm_expr_entry_parse_as_value
		(state->input_entry_2, state->sheet);

311
        parse_output ((GnmGenericToolState *) state, &dao);
312 313 314 315 316 317

	if (prepare_ranges (&sim)) {
		err = (gchar *) N_("Invalid variable range was given");
		goto out;
	}

Morten Welinder's avatar
Morten Welinder committed
318
	w = go_gtk_builder_get_widget (state->gui, "iterations");
319 320
	sim.n_iterations = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w));

Morten Welinder's avatar
Morten Welinder committed
321
	w = go_gtk_builder_get_widget (state->gui, "first_round");
322 323
	sim.first_round = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w)) - 1;

Morten Welinder's avatar
Morten Welinder committed
324
	w = go_gtk_builder_get_widget (state->gui, "last_round");
325 326
	sim.last_round = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w)) - 1;

327
	if (sim.first_round > sim.last_round) {
328 329 330
		err = (gchar *) N_("First round number should be less than or "
				   "equal to the number of the last round."
				   );
331 332
		goto out;
	}
Morten Welinder's avatar
Morten Welinder committed
333

334 335
	current_sim = &sim;

Morten Welinder's avatar
Morten Welinder committed
336
	w = go_gtk_builder_get_widget (state->gui, "max-time");
337 338
	sim.max_time = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w)) - 1;

339
	g_get_current_time (&sim.start);
Morten Welinder's avatar
Morten Welinder committed
340
	err = simulation_tool (GNM_WBC (state->wbcg),
341
			       &dao, &sim);
342 343
	g_get_current_time (&sim.end);

344
	if (err == NULL) {
345
		results_sim_index = sim.first_round;
346
		update_log (state, &sim);
347 348
		update_results_view (&sim);

349
		if (sim.last_round > results_sim_index) {
Morten Welinder's avatar
Morten Welinder committed
350
			w = go_gtk_builder_get_widget (state->gui, "next-button");
351 352 353
			gtk_widget_set_sensitive (w, TRUE);
		}
	}
354 355 356 357 358
 out:
	value_release (sim.inputs);
	value_release (sim.outputs);

	if (err != NULL)
359
		error_in_entry ((GnmGenericToolState *) state,
Marek Černocký's avatar
Marek Černocký committed
360
				GTK_WIDGET (state->input_entry_2), _(err));
361 362 363
	return;
}

364 365 366 367 368 369 370 371 372

/**
 * cb_tool_close_clicked:
 * @button:
 * @state:
 *
 * Close (destroy) the dialog
 **/
static void
Morten Welinder's avatar
Morten Welinder committed
373
cb_tool_cancel_clicked (G_GNUC_UNUSED GtkWidget *button,
374
			GnmGenericToolState *state)
375 376 377 378 379
{
	simulation_tool_destroy (current_sim);
	gtk_widget_destroy (state->dialog);
}

380 381 382 383 384 385 386 387
static void
init_results_view (SimulationState *state)
{
	GtkTextView     *view;
	GtkTextTagTable *tag_table;

	tag_table      = gtk_text_tag_table_new ();
	results_buffer = gtk_text_buffer_new (tag_table);
Morten Welinder's avatar
Morten Welinder committed
388
	view = GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui,
389 390 391 392
						    "results-view"));
	gtk_text_view_set_buffer (view, results_buffer);
}

393 394 395 396 397 398 399 400 401
/**
 * dialog_simulation:
 * @wbcg:
 * @sheet:
 *
 * Show the dialog (guru).
 *
 **/
void
402
dialog_simulation (WBCGtk *wbcg, G_GNUC_UNUSED Sheet *sheet)
403 404 405
{
        SimulationState *state;
	WorkbookControl *wbc;
406
	GtkWidget       *w;
407 408 409

	g_return_if_fail (wbcg != NULL);

Morten Welinder's avatar
Morten Welinder committed
410
	wbc = GNM_WBC (wbcg);
411 412

	/* Only pop up one copy per workbook */
Morten Welinder's avatar
Morten Welinder committed
413
	if (gnm_dialog_raise_if_exists (wbcg, SIMULATION_KEY))
414 415 416 417
		return;

	state = g_new (SimulationState, 1);
	if (dialog_tool_init (state, wbcg, wb_control_cur_sheet (wbc),
418
			      GNUMERIC_HELP_LINK_SIMULATION,
419
			      "res:ui/simulation.ui", "Simulation",
420 421 422
			      _("Could not create the Simulation dialog."),
			      SIMULATION_KEY,
			      G_CALLBACK (simulation_ok_clicked_cb),
423
			      G_CALLBACK (cb_tool_cancel_clicked),
424 425 426 427
			      G_CALLBACK (simulation_update_sensitivity_cb),
			      0))
		return;

428
	init_results_view (state);
429
	current_sim = NULL;
430

Morten Welinder's avatar
Morten Welinder committed
431
	w = go_gtk_builder_get_widget (state->gui, "prev-button");
432
	gtk_widget_set_sensitive (w, FALSE);
433 434
	g_signal_connect_after (G_OBJECT (w), "clicked",
				G_CALLBACK (prev_button_cb), state);
Morten Welinder's avatar
Morten Welinder committed
435
	w = go_gtk_builder_get_widget (state->gui, "next-button");
436 437
	g_signal_connect_after (G_OBJECT (w), "clicked",
				G_CALLBACK (next_button_cb), state);
438
	gtk_widget_set_sensitive (w, FALSE);
Morten Welinder's avatar
Morten Welinder committed
439
	w = go_gtk_builder_get_widget (state->gui, "min-button");
440
	gtk_widget_set_sensitive (w, FALSE);
441
	gtk_widget_hide (w);
Morten Welinder's avatar
Morten Welinder committed
442
	w = go_gtk_builder_get_widget (state->gui, "max-button");
443
	gtk_widget_set_sensitive (w, FALSE);
444
	gtk_widget_hide (w);
445

446 447 448
	gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog), wbcg,
					   GNM_DIALOG_DESTROY_SHEET_REMOVED);

449
	gnm_dao_set_put (GNM_DAO (state->gdao), FALSE, FALSE);
450
	simulation_update_sensitivity_cb (NULL, state);
451
	tool_load_selection ((GnmGenericToolState *)state, TRUE);
452 453
        return;
}