1 /*
2 * ngspice-analysis.c
3 *
4 * Authors:
5 * Marc Lorber <Lorber.Marc@wanadoo.fr>
6 * Bernhard Schuster <bernhard@ahoi.io>
7 * Guido Trentalancia <guido@trentalancia.com>
8 *
9 * Web page: https://ahoi.io/project/oregano
10 *
11 * Copyright (C) 2009-2012 Marc Lorber
12 * Copyright (C) 2014 Bernhard Schuster
13 * Copyright (C) 2017 Guido Trentalancia
14 *
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License as
17 * published by the Free Software Foundation; either version 2 of the
18 * License, or (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public
26 * License along with this program; if not, write to the
27 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31 #include <glib.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <glib/gi18n.h>
37
38 #include "ngspice.h"
39 #include "netlist-helper.h"
40 #include "dialogs.h"
41 #include "engine-internal.h"
42 #include "ngspice-analysis.h"
43 #include "../tools/thread-pipe.h"
44 #include "../tools/cancel-info.h"
45
46 #define SP_TITLE "oregano\n"
47 #define CPU_TIME "CPU time since last call:"
48
49 #define TAGS_COUNT (sizeof(analysis_tags) / sizeof(struct analysis_tag))
50 #include "debug.h"
51 #define IS_THIS_ITEM(str, item) (!strncmp (str, item, strlen (item)))
52 #ifdef DEBUG_THIS
53 #undef DEBUG_THIS
54 #endif
55 #define DEBUG_THIS 1
56
57 /**
58 * \brief extract the resulting variables from ngspice output
59 *
60 * In ngspice a number can terminate only with a space,
61 * while in spice3 a number can also terminate with a
62 * comma.
63 *
64 * In the Fourier analysis the name of the output ends
65 * with a colon.
66 *
67 * Tested function.
68 *
69 * @returns a GArray filled up doubles
70 */
get_variables(const gchar * str,gint * count)71 gchar **get_variables (const gchar *str, gint *count)
72 {
73 g_return_val_if_fail (str, NULL);
74
75 gchar **out;
76 static gchar *tmp[100];
77 const gchar *start, *end;
78 gint i = 0;
79
80 start = str;
81 while (isspace (*start))
82 start++;
83 end = start;
84 while (*end != '\0') {
85 if (isspace (*end) || *end == ',' || *end == ':') {
86 // number ended, designate as such and replace the string
87 tmp[i] = g_strndup (start, (gsize)(end - start));
88 i++;
89 start = end;
90 while (isspace (*start) || *start == ',' || *start == ':')
91 start++;
92 end = start;
93 } else {
94 end++;
95 }
96 }
97 if (end > start) {
98 tmp[i] = g_strndup (start, (gsize)(end - start));
99 i++;
100 }
101
102 if (i == 0) {
103 g_warning ("NO COLUMNS FOUND\n");
104 return NULL;
105 }
106
107 // append an extra NUL slot to allow using g_strfreev
108 out = g_new0 (gchar *, i + 1);
109 (*count) = i;
110 memcpy (out, tmp, sizeof(gchar *) * i);
111 out[i] = NULL;
112 return out;
113 }
114
115 /**
116 * @resources: caller frees
117 */
parse_dc_analysis(NgspiceAnalysisResources * resources)118 static ThreadPipe *parse_dc_analysis (NgspiceAnalysisResources *resources)
119 {
120 ThreadPipe *pipe = resources->pipe;
121 gchar **buf = &resources->buf;
122 const SimSettings* const sim_settings = resources->sim_settings;
123 GList **analysis = resources->analysis;
124 guint *num_analysis = resources->num_analysis;
125
126 static SimulationData *sdata;
127 static Analysis *data;
128 gsize size;
129 gboolean found = FALSE;
130 gchar **variables;
131 gint i, n = 0, index = 0;
132 gdouble val[10];
133 gdouble np1;
134
135 NG_DEBUG ("DC: result str\n>>>\n%s\n<<<", *buf);
136
137 data = g_new0 (Analysis, 1);
138 sdata = SIM_DATA (data);
139 sdata->type = ANALYSIS_TYPE_DC_TRANSFER;
140 sdata->functions = NULL;
141
142 np1 = 1.;
143 ANALYSIS (sdata)->dc.start = sim_settings_get_dc_start (sim_settings);
144 ANALYSIS (sdata)->dc.stop = sim_settings_get_dc_stop (sim_settings);
145 ANALYSIS (sdata)->dc.step = sim_settings_get_dc_step (sim_settings);
146
147 np1 = (ANALYSIS (sdata)->dc.stop - ANALYSIS (sdata)->dc.start) / ANALYSIS (sdata)->dc.step;
148 ANALYSIS (sdata)->dc.sim_length = np1;
149
150 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
151 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
152
153 // Calculates the number of variables
154 variables = get_variables (*buf, &n);
155 if (!variables)
156 return pipe;
157
158 n = n - 1;
159 sdata->var_names = (char **)g_new0 (gpointer, n);
160 sdata->var_units = (char **)g_new0 (gpointer, n);
161 sdata->var_names[0] = g_strdup ("Voltage sweep");
162 sdata->var_units[0] = g_strdup (_ ("voltage"));
163 sdata->var_names[1] = g_strdup (variables[2]);
164 sdata->var_units[1] = g_strdup (_ ("voltage"));
165
166 sdata->n_variables = 2;
167 sdata->got_points = 0;
168 sdata->got_var = 0;
169 sdata->data = (GArray **)g_new0 (gpointer, 2);
170
171 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
172
173 for (i = 0; i < 2; i++)
174 sdata->data[i] = g_array_new (TRUE, TRUE, sizeof(double));
175
176 sdata->min_data = g_new (double, n);
177 sdata->max_data = g_new (double, n);
178
179 // Read the data
180 for (i = 0; i < 2; i++) {
181 sdata->min_data[i] = G_MAXDOUBLE;
182 sdata->max_data[i] = -G_MAXDOUBLE;
183 }
184 found = FALSE;
185 while (((pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size)) != 0) && !found) {
186 if (strlen (*buf) <= 2) {
187 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
188 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
189 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
190 }
191 variables = get_variables (*buf, &i);
192 if (!variables)
193 return pipe;
194 index = atoi (variables[0]);
195 for (i = 0; i < n; i++) {
196 val[i] = g_ascii_strtod (variables[i + 1], NULL);
197 sdata->data[i] = g_array_append_val (sdata->data[i], val[i]);
198 if (val[i] < sdata->min_data[i])
199 sdata->min_data[i] = val[i];
200 if (val[i] > sdata->max_data[i])
201 sdata->max_data[i] = val[i];
202 }
203 sdata->got_points++;
204 sdata->got_var = 2;
205 if (index >= ANALYSIS (sdata)->dc.sim_length)
206 found = TRUE;
207 }
208
209 *analysis = g_list_append (*analysis, sdata);
210 (*num_analysis)++;
211
212 return pipe;
213 }
214
215 /**
216 * @resources: caller frees
217 */
parse_ac_analysis(NgspiceAnalysisResources * resources)218 static ThreadPipe *parse_ac_analysis (NgspiceAnalysisResources *resources)
219 {
220 ThreadPipe *pipe = resources->pipe;
221 gboolean is_vanilla = resources->is_vanilla;
222 gchar *scale, **variables, **buf = &resources->buf;
223 const SimSettings* const sim_settings = resources->sim_settings;
224 GList **analysis = resources->analysis;
225 guint *num_analysis = resources->num_analysis;
226 static SimulationData *sdata;
227 static Analysis *data;
228 gsize size;
229 gboolean found = FALSE;
230 gint i, n = 0, index = 0;
231 gdouble fstart, fstop, val[10];
232
233 NG_DEBUG ("AC: result str\n>>>\n%s\n<<<", *buf);
234
235 data = g_new0 (Analysis, 1);
236 sdata = SIM_DATA (data);
237 sdata->type = ANALYSIS_TYPE_AC;
238 sdata->functions = NULL;
239
240 ANALYSIS (sdata)->ac.sim_length = 1.;
241
242 ANALYSIS (sdata)->ac.start = fstart = sim_settings_get_ac_start (sim_settings);
243 ANALYSIS (sdata)->ac.stop = fstop = sim_settings_get_ac_stop (sim_settings);
244
245 scale = sim_settings_get_ac_type (sim_settings);
246 if (!g_ascii_strcasecmp (scale, "LIN")) {
247 ANALYSIS (sdata)->ac.sim_length = (double) sim_settings_get_ac_npoints (sim_settings);
248 } else if (!g_ascii_strcasecmp (scale, "DEC")) {
249 ANALYSIS (sdata)->ac.sim_length = (double) sim_settings_get_ac_npoints (sim_settings) * log10 (fstop / fstart);
250 } else if (!g_ascii_strcasecmp (scale, "OCT")) {
251 ANALYSIS (sdata)->ac.sim_length = (double) sim_settings_get_ac_npoints (sim_settings) * log10 (fstop / fstart) / log10 (2);
252 }
253
254 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
255 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
256
257 // Calculates the number of variables
258 variables = get_variables (*buf, &n);
259 if (!variables)
260 return pipe;
261
262 n = n - 1;
263 sdata->var_names = (char **)g_new0 (gpointer, n);
264 sdata->var_units = (char **)g_new0 (gpointer, n);
265 sdata->var_names[0] = g_strdup ("Frequency");
266 sdata->var_units[0] = g_strdup (_ ("frequency"));
267 sdata->var_names[1] = g_strdup (variables[2]);
268 sdata->var_units[1] = g_strdup (_ ("voltage"));
269
270 sdata->n_variables = 2;
271 sdata->got_points = 0;
272 sdata->got_var = 0;
273 sdata->data = (GArray **)g_new0 (gpointer, 2);
274
275 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
276
277 for (i = 0; i < 2; i++)
278 sdata->data[i] = g_array_new (TRUE, TRUE, sizeof(double));
279
280 sdata->min_data = g_new (double, n);
281 sdata->max_data = g_new (double, n);
282
283 // Read the data
284 for (i = 0; i < 2; i++) {
285 sdata->min_data[i] = G_MAXDOUBLE;
286 sdata->max_data[i] = -G_MAXDOUBLE;
287 }
288 found = FALSE;
289 while (((pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size)) != 0) && !found) {
290 if (strlen (*buf) <= 2) {
291 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
292 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
293 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
294 }
295
296 variables = get_variables (*buf, &i);
297 if (!variables)
298 return pipe;
299
300 index = atoi (variables[0]);
301 for (i = 0; i < n; i++) {
302 if (i == 0)
303 val[i] = g_ascii_strtod (variables[i + 1], NULL);
304 if (is_vanilla && i > 0)
305 val[i] = g_ascii_strtod (variables[i + 2], NULL);
306 else
307 val[i] = g_ascii_strtod (variables[i + 1], NULL);
308 sdata->data[i] = g_array_append_val (sdata->data[i], val[i]);
309 if (val[i] < sdata->min_data[i])
310 sdata->min_data[i] = val[i];
311 if (val[i] > sdata->max_data[i])
312 sdata->max_data[i] = val[i];
313 }
314 sdata->got_points++;
315 sdata->got_var = 2;
316 if (index >= ANALYSIS (sdata)->ac.sim_length - 1)
317 found = TRUE;
318 }
319
320 *analysis = g_list_append (*analysis, sdata);
321 (*num_analysis)++;
322
323 return pipe;
324 }
325
326 /**
327 * Structure that helps parsing the output of ngspice.
328 *
329 * @name: The name (handled like an ID) of the column.
330 * @unit: The physical unit. it can have one of the following values:
331 * "none", "time", "voltage", "current", "unknown"
332 * @data: The data.
333 * @min: The min value of the data.
334 * @max: The max value of the data.
335 */
336 typedef struct {
337 GString *name;
338 GString *unit;
339 GArray *data;
340 gdouble min;
341 gdouble max;
342 } NgspiceColumn;
343
344 /**
345 * Structure that helps parsing the output of ngspice.
346 *
347 * @ngspice_columns: The columns.
348 * @current_variables: New columns are appended to the end
349 * of the columns array. Because the index- and time-column
350 * are displayed repeatedly with every 3 new columns, there
351 * is needed an information how to interpret the incoming
352 * data to append it to the right columns. The current_variables
353 * array maps the position of the input data in the file
354 * to the position of the related column position in the table.
355 */
356 typedef struct {
357 GPtrArray *ngspice_columns;
358 GArray *current_variables;
359 } NgspiceTable;
360
361 /**
362 * Allocates memory.
363 *
364 * @name: The name (ID) of the new column
365 * @predicted_size: The predicted end size of the new column.
366 */
ngspice_column_new(const gchar * name,guint predicted_size)367 static NgspiceColumn *ngspice_column_new(const gchar *name, guint predicted_size) {
368 NgspiceColumn *ret_val = malloc(sizeof(NgspiceColumn));
369
370 ret_val->name = g_string_new(name);
371
372 if (g_str_has_prefix(name, "Index"))
373 ret_val->unit = g_string_new("none");
374 else if (g_str_has_prefix(name, "time"))
375 ret_val->unit = g_string_new("time");
376 else if (g_str_has_prefix(name, "V") || g_str_has_prefix(name, "v"))
377 ret_val->unit = g_string_new("voltage");
378 else if (g_str_has_suffix(name, "#branch"))
379 ret_val->unit = g_string_new("current");
380 else
381 ret_val->unit = g_string_new("unknown");
382
383 ret_val->data = g_array_sized_new(TRUE, TRUE, sizeof(gdouble), predicted_size);
384 ret_val->min = G_MAXDOUBLE;
385 ret_val->max = -G_MAXDOUBLE;
386
387 return ret_val;
388 }
389
390 /**
391 * Frees memory.
392 */
ngspice_column_destroy(gpointer ptr)393 static void ngspice_column_destroy(gpointer ptr) {
394 NgspiceColumn *column = (NgspiceColumn *)ptr;
395 if (column == NULL)
396 return;
397 if (column->name != NULL)
398 g_string_free(column->name, TRUE);
399 if (column->unit != NULL)
400 g_string_free(column->unit, TRUE);
401 g_free(column);
402 }
403
404 /**
405 * Allocates memory.
406 */
ngspice_table_new()407 static NgspiceTable *ngspice_table_new() {
408 NgspiceTable *ret_val = malloc(sizeof(NgspiceTable));
409 ret_val->ngspice_columns = g_ptr_array_new_with_free_func(ngspice_column_destroy);
410 ret_val->current_variables = g_array_new(TRUE, TRUE, sizeof(guint));
411 return ret_val;
412 }
413
414 /**
415 * Frees the memory.
416 */
ngspice_table_destroy(NgspiceTable * ngspice_table)417 static void ngspice_table_destroy(NgspiceTable *ngspice_table) {
418
419 if (ngspice_table->ngspice_columns != NULL) {
420 guint len = ngspice_table->ngspice_columns->len;
421
422 for (int i = 0; i < len; i++) {
423 NgspiceColumn *column = ngspice_table->ngspice_columns->pdata[i];
424 if (column != NULL)
425 g_array_free(column->data, TRUE);
426 }
427
428 g_ptr_array_free(ngspice_table->ngspice_columns, TRUE);
429 }
430
431 if (ngspice_table->current_variables != NULL)
432 g_array_free(ngspice_table->current_variables, TRUE);
433
434 g_free(ngspice_table);
435 }
436
437 /**
438 * Converts "x...x#branch" to "I(x...x)".
439 *
440 * Let "x...x" be the name of a part in the ngspice netlist,
441 * then the current variable (current, that flows through
442 * that part) of that part is named
443 * "x...x#branch" in ngspice. To shorten the name and make
444 * it prettier, the name will be displayed as "I(x...x)"
445 * in Oregano (analogous to V(node_nr)).
446 */
convert_variable_name(gchar ** variable)447 static void convert_variable_name(gchar **variable) {
448 gchar **splitted = g_regex_split_simple("\\#branch", *variable, 0, 0);
449 g_free(*variable);
450 *variable = g_strdup_printf("I(%s)", *splitted);
451 g_strfreev(splitted);
452 }
453
454 /**
455 * Creates and appends new columns to the table, if there are new variables.
456 * The referenced columns are now the newly added columns.
457 *
458 * @predicted_size: The predicted end size of the new columns. It is assumed
459 * that the new columns will have the same size.
460 */
ngspice_table_new_columns(NgspiceTable * ngspice_table,gchar ** variables,guint predicted_size)461 static void ngspice_table_new_columns(NgspiceTable *ngspice_table, gchar **variables, guint predicted_size) {
462 if (ngspice_table->ngspice_columns->len > 0) {
463 NgspiceColumn *column = (NgspiceColumn *)ngspice_table->ngspice_columns->pdata[0];
464 predicted_size = column->data->len;
465 }
466
467 g_array_free(ngspice_table->current_variables, TRUE);
468 ngspice_table->current_variables = g_array_new(TRUE, TRUE, sizeof(guint));
469
470 for (gchar **variable = variables; *variable != NULL && **variable != '\n' && **variable != 0; variable++) {
471 if (g_str_has_suffix(*variable, "#branch"))
472 convert_variable_name(variable);
473 int i;
474 for (i = 0; i < ngspice_table->ngspice_columns->len; i++) {
475 if (!strcmp(*variable, ((NgspiceColumn *)ngspice_table->ngspice_columns->pdata[i])->name->str))
476 break;
477 }
478 if (i == ngspice_table->ngspice_columns->len) {
479 NgspiceColumn *new_column = ngspice_column_new(*variable, predicted_size);
480 g_ptr_array_add(ngspice_table->ngspice_columns, new_column);
481 }
482 g_array_append_val(ngspice_table->current_variables, i);
483 }
484 }
485
486 /**
487 * Appends a split line to the end of the current referenced columns.
488 */
ngspice_table_add_data(NgspiceTable * ngspice_table,gchar ** data)489 static void ngspice_table_add_data(NgspiceTable *ngspice_table, gchar **data) {
490 g_return_if_fail(data != NULL);
491 g_return_if_fail(*data != NULL);
492 if (**data == 0)
493 return;
494 g_return_if_fail(ngspice_table != NULL);
495
496 guint64 index = g_ascii_strtoull(*data, NULL, 10);
497 for (int i = 0; data[i] != NULL && data[i][0] != 0 && data[i][0] != '\n' && i < ngspice_table->current_variables->len; i++) {
498 guint column_index = g_array_index(ngspice_table->current_variables, guint, i);
499 NgspiceColumn *column = (NgspiceColumn*)(ngspice_table->ngspice_columns->pdata[column_index]);
500 if (column->data->len > index) {
501 continue;//assert equal
502 }
503 gdouble new_content = g_ascii_strtod(data[i], NULL);
504 g_array_append_val(column->data, new_content);
505 if (new_content < column->min)
506 column->min = new_content;
507 if (new_content > column->max)
508 column->max = new_content;
509 }
510 }
511
512 /**
513 * Counts the number of different guints. If a certain
514 * guint occurs more than once, it is counted as one.
515 */
get_real_len(GArray * array_guint)516 static guint get_real_len(GArray *array_guint) {
517 guint ret_val = 0;
518 for (guint i = 0; i < array_guint->len; i++) {
519 ret_val++;
520 for (guint j = 0; j < i; j++) {
521 if (g_array_index(array_guint, guint, i) == g_array_index(array_guint, guint, j)) {
522 ret_val--;
523 break;
524 }
525 }
526 }
527
528 return ret_val;
529 }
530
531 /**
532 * Returns the index that is currently parsed.
533 *
534 * The first referenced column is the string-index column,
535 * the second referenced column is the time. The index and
536 * the time column can already be filled up by earlier
537 * iterations, so the length of the third referenced column
538 * represents the index that is currently parsed.
539 */
get_current_index(NgspiceTable * ngspice_table)540 static guint get_current_index(NgspiceTable *ngspice_table) {
541 guint column_nr = g_array_index(ngspice_table->current_variables, guint, 2);
542 NgspiceColumn *column = g_ptr_array_index(ngspice_table->ngspice_columns, column_nr);
543 return column->data->len;
544 }
545
546 typedef struct {
547 NgspiceTable *table;
548 ThreadPipe *pipe;
549 gboolean is_cancel;
550 } ParseTransientAnalysisReturnResources;
551
552 /**
553 * @resources: caller frees
554 */
parse_transient_analysis_resources(NgspiceAnalysisResources * resources)555 static ParseTransientAnalysisReturnResources parse_transient_analysis_resources (NgspiceAnalysisResources *resources)
556 {
557 gchar **buf = &resources->buf;
558 const SimSettings *sim_settings = resources->sim_settings;
559 guint *num_analysis = resources->num_analysis;
560 ProgressResources *progress_reader = resources->progress_reader;
561 guint64 no_of_data_rows = resources->no_of_data_rows_transient;
562 guint no_of_variables = resources->no_of_variables;
563
564
565 enum STATE {
566 NGSPICE_ANALYSIS_STATE_READ_DATA,
567 NGSPICE_ANALYSIS_STATE_DATA_SMALL_BLOCK_END,
568 NGSPICE_ANALYSIS_STATE_DATA_LARGE_BLOCK_END,
569 NGSPICE_ANALYSIS_STATE_DATA_END,
570 NGSPICE_ANALYSIS_STATE_READ_VARIABLES_NEW,
571 NGSPICE_ANALYSIS_STATE_READ_VARIABLES_OLD
572 };
573
574 g_mutex_lock(&progress_reader->progress_mutex);
575 progress_reader->progress = 0;
576 progress_reader->time = g_get_monotonic_time();
577 g_mutex_unlock(&progress_reader->progress_mutex);
578
579 gsize size;
580
581 NgspiceTable *ngspice_table = ngspice_table_new();
582
583 ParseTransientAnalysisReturnResources ret_val;
584 ret_val.table = ngspice_table;
585 ret_val.pipe = resources->pipe;
586 ret_val.is_cancel = TRUE;
587
588 enum STATE state = NGSPICE_ANALYSIS_STATE_DATA_LARGE_BLOCK_END;
589
590 guint i = 0;
591
592 do {
593 if (i % 50 == 0 && cancel_info_is_cancel(resources->cancel_info))
594 return ret_val;
595
596 switch (state) {
597 case NGSPICE_ANALYSIS_STATE_READ_VARIABLES_NEW:
598 {
599 gchar **splitted_line = g_regex_split_simple(" +", *buf, 0, 0);
600
601 ngspice_table_new_columns(ngspice_table, splitted_line, no_of_data_rows);
602
603 g_strfreev(splitted_line);
604
605 state = NGSPICE_ANALYSIS_STATE_READ_VARIABLES_OLD;
606 break;
607 }
608 case NGSPICE_ANALYSIS_STATE_READ_DATA:
609 {
610 gchar **splitted_line = g_regex_split_simple("\\t+|-{2,}", *buf, 0, 0);
611
612 ngspice_table_add_data(ngspice_table, splitted_line);
613
614 g_strfreev(splitted_line);
615
616 if ((ret_val.pipe = thread_pipe_pop(ret_val.pipe, (gpointer *)buf, &size)) == NULL)
617 return ret_val;
618
619 switch (*buf[0]) {
620 case '\f':{
621 // estimate progress begin
622 guint len_of_current_variables = get_real_len(ngspice_table->current_variables) - 2;
623 guint len_of_current_columns = ngspice_table->ngspice_columns->len - 2;
624 guint count_of_variables_already_finished = len_of_current_columns - len_of_current_variables;
625 g_mutex_lock(&progress_reader->progress_mutex);
626 progress_reader->progress = (double)count_of_variables_already_finished / (double)no_of_variables +
627 (double)len_of_current_variables / (double)no_of_variables *
628 (double)get_current_index(ngspice_table) / (double)no_of_data_rows;
629 progress_reader->time = g_get_monotonic_time();
630 g_mutex_unlock(&progress_reader->progress_mutex);
631 // estimate progress end
632
633 state = NGSPICE_ANALYSIS_STATE_DATA_SMALL_BLOCK_END;
634 break;}
635 case '\n':
636 state = NGSPICE_ANALYSIS_STATE_DATA_END;
637 break;
638 }
639 break;
640 }
641 case NGSPICE_ANALYSIS_STATE_READ_VARIABLES_OLD:
642 {
643 if ((ret_val.pipe = thread_pipe_pop(ret_val.pipe, (gpointer *)buf, &size)) == NULL)
644 return ret_val;
645
646 if (*buf[0] != '-')
647 state = NGSPICE_ANALYSIS_STATE_READ_DATA;
648 break;
649 }
650 case NGSPICE_ANALYSIS_STATE_DATA_SMALL_BLOCK_END:
651 {
652 if ((ret_val.pipe = thread_pipe_pop(ret_val.pipe, (gpointer *)buf, &size)) == NULL)
653 return ret_val;
654
655 switch (*buf[0]) {
656 case 'I':
657 state = NGSPICE_ANALYSIS_STATE_READ_VARIABLES_OLD;
658 break;
659 case ' ':
660 state = NGSPICE_ANALYSIS_STATE_DATA_LARGE_BLOCK_END;
661 break;
662 }
663 break;
664 }
665 case NGSPICE_ANALYSIS_STATE_DATA_LARGE_BLOCK_END:
666 {
667 if ((ret_val.pipe = thread_pipe_pop(ret_val.pipe, (gpointer *)buf, &size)) == NULL)
668 return ret_val;
669
670 if (*buf[0] == 'I')
671 state = NGSPICE_ANALYSIS_STATE_READ_VARIABLES_NEW;
672 break;
673 }
674 case NGSPICE_ANALYSIS_STATE_DATA_END:
675 break;
676 }
677 } while (state != NGSPICE_ANALYSIS_STATE_DATA_END);
678
679 ret_val.is_cancel = FALSE;
680
681 SimulationData *sdata = SIM_DATA (g_new0 (Analysis, 1));
682 sdata->type = ANALYSIS_TYPE_TRANSIENT;
683 sdata->functions = NULL;
684
685 gint nodes_nb = ngspice_table->ngspice_columns->len - 2;
686
687 ANALYSIS (sdata)->transient.sim_length =
688 sim_settings_get_trans_stop (sim_settings) - sim_settings_get_trans_start (sim_settings);
689 ANALYSIS (sdata)->transient.step_size = sim_settings_get_trans_step (sim_settings);
690
691 sdata->var_names = g_new0 (gchar *, nodes_nb + 1);
692 sdata->var_units = g_new0 (gchar *, nodes_nb + 1);
693 sdata->data = g_new0 (GArray *, nodes_nb + 1);
694 sdata->min_data = g_new (gdouble, nodes_nb + 1);
695 sdata->max_data = g_new (gdouble, nodes_nb + 1);
696
697 for (int i = 0; i < nodes_nb + 1; i++) {
698 NgspiceColumn *column = ngspice_table->ngspice_columns->pdata[i+1];
699 sdata->var_names[i] = g_strdup(column->name->str);
700 sdata->var_units[i] = g_strdup(column->unit->str);
701 sdata->data[i] = column->data;
702 sdata->min_data[i] = column->min;
703 sdata->max_data[i] = column->max;
704
705 ngspice_table->ngspice_columns->pdata[i+1] = NULL;
706 }
707 sdata->n_variables = nodes_nb + 1;
708 NgspiceColumn *column = ngspice_table->ngspice_columns->pdata[0];
709 sdata->got_points = column->data->len;
710 sdata->got_var = nodes_nb + 1;
711
712 (*num_analysis)++;
713 *resources->analysis = g_list_append (*resources->analysis, sdata);
714
715 return ret_val;
716 }
717
parse_transient_analysis(NgspiceAnalysisResources * resources)718 static ThreadPipe *parse_transient_analysis (NgspiceAnalysisResources *resources) {
719 ParseTransientAnalysisReturnResources ret_res = parse_transient_analysis_resources(resources);
720
721 ngspice_table_destroy(ret_res.table);
722
723 if (ret_res.is_cancel && ret_res.pipe) {
724 thread_pipe_set_read_eof(ret_res.pipe);
725 ret_res.pipe = NULL;
726 }
727
728 return ret_res.pipe;
729 }
730
731 /**
732 * @resources: caller frees
733 */
parse_fourier_analysis(NgspiceAnalysisResources * resources)734 static ThreadPipe *parse_fourier_analysis (NgspiceAnalysisResources *resources)
735 {
736 ThreadPipe *pipe = resources->pipe;
737 gchar **buf = &resources->buf;
738 const SimSettings *sim_settings = resources->sim_settings;
739 GList **analysis = resources->analysis;
740 guint *num_analysis = resources->num_analysis;
741
742 static SimulationData *sdata;
743 static Analysis *data;
744 gsize size;
745 gchar **variables;
746 gint i, n = 0, j, k;
747 gdouble val[3];
748 gchar **node_ids;
749 gchar *vout;
750
751 NG_DEBUG ("FOURIER: result str\n>>>\n%s\n<<<", *buf);
752
753 data = g_new0 (Analysis, 1);
754 sdata = SIM_DATA (data);
755 sdata->type = ANALYSIS_TYPE_FOURIER;
756 sdata->functions = NULL;
757
758 g_strchug (*buf);
759 ANALYSIS (sdata)->fourier.freq = sim_settings_get_fourier_frequency (sim_settings);
760
761 vout = sim_settings_get_fourier_vout (sim_settings);
762 node_ids = g_strsplit (vout, " ", 0);
763 for (i = 0; node_ids[i] != NULL; i++) {
764 }
765 g_strfreev (node_ids);
766 g_free (vout);
767 ANALYSIS (sdata)->fourier.nb_var = i + 1;
768 n = ANALYSIS (sdata)->fourier.nb_var;
769 sdata->n_variables = n;
770
771 sdata->var_names = (char **)g_new0 (gpointer, n);
772 sdata->var_units = (char **)g_new0 (gpointer, n);
773 sdata->var_names[0] = g_strdup ("Frequency");
774 sdata->var_units[0] = g_strdup (_ ("frequency"));
775
776 sdata->got_points = 0;
777 sdata->got_var = 0;
778
779 sdata->data = (GArray **)g_new0 (gpointer, n);
780 for (i = 0; i < n; i++)
781 sdata->data[i] = g_array_new (TRUE, TRUE, sizeof(double));
782
783 sdata->min_data = g_new (double, n);
784 sdata->max_data = g_new (double, n);
785
786 for (i = 0; i < n; i++) {
787 sdata->min_data[i] = G_MAXDOUBLE;
788 sdata->max_data[i] = -G_MAXDOUBLE;
789 }
790
791 // For each output voltage (plus the frequency for the x-axis),
792 // scan its data set
793 for (k = 1; k < n; k++) {
794 variables = get_variables (*buf, &i);
795 if (!variables)
796 return pipe;
797
798 // Skip data set header (4 lines)
799 for (i = 0; i < 4; i++)
800 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
801
802 sdata->var_names[k] = g_strdup_printf ("mag(%s)", variables[3]);
803 sdata->var_units[k] = g_strdup (_ ("voltage"));
804
805 // Scan data set for 10 harmonics
806 for (j = 0; j < 10; j++) {
807 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
808 if (!pipe)
809 return pipe;
810
811 sscanf (*buf, "\t%d\t%lf\t%lf\t%lf", &i, &val[0], &val[1], &val[2]);
812 if (k == 1) {
813 sdata->data[0] = g_array_append_val (sdata->data[0], val[0]);
814 if (val[0] < sdata->min_data[0])
815 sdata->min_data[0] = val[0];
816 if (val[0] > sdata->max_data[0])
817 sdata->max_data[0] = val[0];
818 sdata->data[1] = g_array_append_val (sdata->data[1], val[1]);
819 if (val[1] < sdata->min_data[1])
820 sdata->min_data[1] = val[1];
821 if (val[1] > sdata->max_data[1])
822 sdata->max_data[1] = val[1];
823 sdata->got_points = sdata->got_points + 2;
824 } else {
825 sdata->data[k] = g_array_append_val (sdata->data[k], val[1]);
826 if (val[1] < sdata->min_data[k])
827 sdata->min_data[k] = val[1];
828 if (val[1] > sdata->max_data[k])
829 sdata->max_data[k] = val[1];
830 sdata->got_points++;
831 }
832 }
833 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
834 if (!pipe)
835 return pipe;
836
837 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
838 if (!pipe)
839 return pipe;
840
841 }
842
843 sdata->got_var = n;
844
845 *analysis = g_list_append (*analysis, sdata);
846 (*num_analysis)++;
847
848 return pipe;
849 }
850
851 /**
852 * @resources: caller frees
853 */
parse_noise_analysis(NgspiceAnalysisResources * resources)854 static ThreadPipe *parse_noise_analysis (NgspiceAnalysisResources *resources)
855 {
856 ThreadPipe *pipe = resources->pipe;
857 gboolean is_vanilla = resources->is_vanilla;
858 gchar *scale, **variables, **buf = &resources->buf;
859 const SimSettings* const sim_settings = resources->sim_settings;
860 GList **analysis = resources->analysis;
861 guint *num_analysis = resources->num_analysis;
862 static SimulationData *sdata;
863 static Analysis *data;
864 gsize size;
865 gboolean found = FALSE;
866 gint i, n = 0, index = 0;
867 gdouble fstart, fstop, val[10];
868
869 NG_DEBUG ("NOISE: result str\n>>>\n%s\n<<<", *buf);
870
871 data = g_new0 (Analysis, 1);
872 sdata = SIM_DATA (data);
873 sdata->type = ANALYSIS_TYPE_NOISE;
874 sdata->functions = NULL;
875
876 ANALYSIS (sdata)->noise.sim_length = 1.;
877
878 ANALYSIS (sdata)->noise.start = fstart = sim_settings_get_noise_start (sim_settings);
879 ANALYSIS (sdata)->noise.stop = fstop = sim_settings_get_noise_stop (sim_settings);
880
881 scale = sim_settings_get_noise_type (sim_settings);
882 if (!g_ascii_strcasecmp (scale, "LIN")) {
883 ANALYSIS (sdata)->noise.sim_length = (double) sim_settings_get_noise_npoints (sim_settings);
884 } else if (!g_ascii_strcasecmp (scale, "DEC")) {
885 ANALYSIS (sdata)->noise.sim_length = (double) sim_settings_get_noise_npoints (sim_settings) * log10 (fstop / fstart);
886 } else if (!g_ascii_strcasecmp (scale, "OCT")) {
887 ANALYSIS (sdata)->noise.sim_length = (double) sim_settings_get_noise_npoints (sim_settings) * log10 (fstop / fstart) / log10 (2);
888 }
889
890 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
891 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
892
893 // Calculates the number of variables
894 variables = get_variables (*buf, &n);
895 if (!variables)
896 return pipe;
897
898 n = n - 1;
899 sdata->var_names = (char **)g_new0 (gpointer, 3);
900 sdata->var_units = (char **)g_new0 (gpointer, 3);
901 sdata->var_names[0] = g_strdup ("Frequency");
902 sdata->var_units[0] = g_strdup (_ ("frequency"));
903 sdata->var_names[1] = g_strdup ("Input Noise Spectrum");
904 sdata->var_units[1] = g_strdup (_ ("psd"));
905 sdata->var_names[2] = g_strdup ("Output Noise Spectrum");
906 sdata->var_units[2] = g_strdup (_ ("psd"));
907
908 sdata->n_variables = 3;
909 sdata->got_points = 0;
910 sdata->got_var = 0;
911 sdata->data = (GArray **)g_new0 (gpointer, 3);
912
913 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
914
915 for (i = 0; i < 3; i++)
916 sdata->data[i] = g_array_new (TRUE, TRUE, sizeof(double));
917
918 sdata->min_data = g_new (double, 3);
919 sdata->max_data = g_new (double, 3);
920
921 // Read the data
922 for (i = 0; i < 3; i++) {
923 sdata->min_data[i] = G_MAXDOUBLE;
924 sdata->max_data[i] = -G_MAXDOUBLE;
925 }
926 found = FALSE;
927 while (!found && ((pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size)) != 0)) {
928 if (strlen (*buf) <= 2) {
929 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
930 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
931 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
932 }
933
934 variables = get_variables (*buf, &i);
935 if (!variables)
936 return pipe;
937
938 index = atoi (variables[0]);
939 for (i = 0; i < 3; i++) {
940 val[i] = g_ascii_strtod (variables[i + 1], NULL);
941 sdata->data[i] = g_array_append_val (sdata->data[i], val[i]);
942 if (val[i] < sdata->min_data[i])
943 sdata->min_data[i] = val[i];
944 if (val[i] > sdata->max_data[i])
945 sdata->max_data[i] = val[i];
946 }
947 sdata->got_points++;
948
949 if (index >= ANALYSIS (sdata)->noise.sim_length - 1)
950 found = TRUE;
951 }
952
953 // Spice 3f5 is affected by a sort of bug and prints extra data
954 if (is_vanilla) {
955 while (((pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size)) != 0)) {
956 if (strlen (*buf) < 2)
957 break;
958 }
959 }
960
961 sdata->got_var = 3;
962
963 *analysis = g_list_append (*analysis, sdata);
964 (*num_analysis)++;
965
966 return pipe;
967 }
968
969 /**
970 * Stores all the data coming through the given pipe to the given file.
971 * This is needed because in case of failure, ngspice prints additional
972 * error information to stdout.
973 *
974 * Maybe the user wants to analyze the ngspice output by external software.
975 */
ngspice_save(const gchar * path_to_file,ThreadPipe * pipe,CancelInfo * cancel_info)976 void ngspice_save (const gchar *path_to_file, ThreadPipe *pipe, CancelInfo *cancel_info)
977 {
978 FILE *file = fopen(path_to_file, "w");
979 gpointer buf = NULL;
980 gsize size;
981
982 for (int i = 0; (pipe = thread_pipe_pop(pipe, &buf, &size)) != NULL; i++) {
983 if (size != 0)
984 fwrite(buf, 1, size, file);
985 /**
986 * cancel_info uses mutex operations, so it shouldn't be
987 * called to often.
988 */
989 if (i % 50 == 0 && cancel_info_is_cancel(cancel_info))
990 break;
991 }
992 fclose(file);
993 if (pipe != NULL)
994 thread_pipe_set_read_eof(pipe);
995 }
996
get_analysis_type(gchar * buf_in,AnalysisType * type_out)997 static gboolean get_analysis_type(gchar *buf_in, AnalysisType *type_out) {
998
999 int i = 0;
1000 gchar *analysis_name = oregano_engine_get_analysis_name_by_type(i);
1001 while (analysis_name) {
1002 if (g_str_has_prefix (buf_in, analysis_name)) {
1003 *type_out = i;
1004 g_free(analysis_name);
1005 return TRUE;
1006 }
1007 g_free(analysis_name);
1008
1009 i++;
1010 analysis_name = oregano_engine_get_analysis_name_by_type(i);
1011 }
1012 return FALSE;
1013 }
1014
parse_no_of_data_rows(gchar * line)1015 static guint64 parse_no_of_data_rows(gchar *line) {
1016 gchar **splitted = g_regex_split_simple("No\\. of Data Rows \\: \\D*(\\d+)\\n", line, 0, 0);
1017 guint64 no_of_data_rows = g_ascii_strtoull(splitted[1], NULL, 10);
1018 g_strfreev(splitted);
1019
1020 return no_of_data_rows;
1021 }
1022
parse_no_of_variables(ThreadPipe * pipe,gchar ** buf)1023 static guint parse_no_of_variables(ThreadPipe *pipe, gchar **buf) {
1024 gsize size;
1025
1026 for (int i = 0; i < 5; i++)
1027 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
1028
1029 guint no_of_variables = 0;
1030 while (**buf != '\n') {
1031 no_of_variables++;
1032 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
1033 }
1034
1035 return no_of_variables;
1036 }
1037
1038 /**
1039 * @resources: caller frees
1040 */
ngspice_analysis(NgspiceAnalysisResources * resources)1041 void ngspice_analysis (NgspiceAnalysisResources *resources)
1042 {
1043 ThreadPipe *pipe = resources->pipe;
1044 const SimSettings *sim_settings = resources->sim_settings;
1045 AnalysisTypeShared *current = resources->current;
1046 gboolean is_vanilla = resources->is_vanilla;
1047 gchar **buf = &resources->buf;
1048 gsize size;
1049 gboolean end_of_output;
1050 gboolean transient_enabled = sim_settings_get_trans (sim_settings);
1051 gboolean fourier_enabled = sim_settings_get_fourier (sim_settings);
1052 gboolean dc_enabled = sim_settings_get_dc (sim_settings);
1053 gboolean ac_enabled = sim_settings_get_ac (sim_settings);
1054 gboolean noise_enabled = sim_settings_get_noise (sim_settings);
1055
1056 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1057 return;
1058
1059 if (!is_vanilla) {
1060 // Get the number of AC Analysis data rows
1061 while (ac_enabled && !g_str_has_prefix (*buf, "No. of Data Rows : ")) {
1062 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1063 return;
1064 }
1065 if (ac_enabled && g_str_has_prefix(*buf, "No. of Data Rows : "))
1066 resources->no_of_data_rows_ac = parse_no_of_data_rows(*buf);
1067
1068 if (ac_enabled && thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1069 return;
1070
1071 // Get the number of DC Analysis data rows
1072 while (dc_enabled && !g_str_has_prefix (*buf, "No. of Data Rows : ")) {
1073 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1074 return;
1075 }
1076 if (dc_enabled && g_str_has_prefix(*buf, "No. of Data Rows : "))
1077 resources->no_of_data_rows_dc = parse_no_of_data_rows(*buf);
1078
1079 if (dc_enabled && thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1080 return;
1081
1082 // Get the number of Operating Point Analysis data rows
1083 while (!g_str_has_prefix (*buf, "No. of Data Rows : ")) {
1084 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1085 return;
1086 }
1087 if (g_str_has_prefix(*buf, "No. of Data Rows : "))
1088 resources->no_of_data_rows_op = parse_no_of_data_rows(*buf);
1089
1090 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1091 return;
1092
1093 // Get the number of Transient Analysis variables
1094 while (transient_enabled && !g_str_has_prefix (*buf, "Initial Transient Solution")) {
1095 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1096 return;
1097 }
1098 if (transient_enabled && g_str_has_prefix(*buf, "Initial Transient Solution"))
1099 resources->no_of_variables = parse_no_of_variables(pipe, buf);
1100
1101 if (transient_enabled && thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1102 return;
1103
1104 // Get the number of Transient Analysis data rows
1105 while (transient_enabled && !g_str_has_prefix (*buf, "No. of Data Rows : ")) {
1106 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1107 return;
1108 }
1109 if (transient_enabled && g_str_has_prefix(*buf, "No. of Data Rows : "))
1110 resources->no_of_data_rows_transient = parse_no_of_data_rows(*buf);
1111
1112 if (transient_enabled && thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1113 return;
1114
1115 // Get the number of Noise Analysis data rows
1116 while (noise_enabled && !g_str_has_prefix (*buf, "No. of Data Rows : ")) {
1117 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1118 return;
1119 }
1120 if (noise_enabled && g_str_has_prefix(*buf, "No. of Data Rows : "))
1121 resources->no_of_data_rows_noise = parse_no_of_data_rows(*buf);
1122 } else {
1123 while (!g_str_has_prefix (*buf, "Operating point information:")) {
1124 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1125 return;
1126 }
1127 }
1128
1129 while (!g_str_has_suffix (*buf, SP_TITLE)) {
1130 if (thread_pipe_pop(pipe, (gpointer *)buf, &size) == NULL)
1131 return;
1132 }
1133
1134 end_of_output = FALSE;
1135 for (int i = 0; transient_enabled || fourier_enabled || dc_enabled || ac_enabled || noise_enabled; i++) {
1136
1137 AnalysisType analysis_type = ANALYSIS_TYPE_NONE;
1138
1139 do {
1140 pipe = thread_pipe_pop(pipe, (gpointer *)buf, &size);
1141 g_strstrip (*buf);
1142 if (!g_ascii_strncasecmp (*buf, "CPU time", 8))
1143 end_of_output = TRUE;
1144 NG_DEBUG ("%d buf = %s", i, *buf);
1145 } while (pipe != NULL && !end_of_output && (*buf[0] == '*' || *buf[0] == '\0'));
1146
1147 // The simulation has finished: no more analysis to parse
1148 if (end_of_output)
1149 break;
1150
1151 if (!get_analysis_type(*buf, &analysis_type) && i == 0) {
1152 oregano_warning ("No analysis found");
1153 break;
1154 }
1155
1156 gboolean unexpected_analysis_found = FALSE;
1157
1158 g_mutex_lock(¤t->mutex);
1159 current->type = analysis_type;
1160 g_mutex_unlock(¤t->mutex);
1161
1162 switch (analysis_type) {
1163 case ANALYSIS_TYPE_TRANSIENT:
1164 pipe = parse_transient_analysis (resources);
1165 transient_enabled = FALSE;
1166 break;
1167 case ANALYSIS_TYPE_FOURIER:
1168 pipe = parse_fourier_analysis (resources);
1169 fourier_enabled = FALSE;
1170 break;
1171 case ANALYSIS_TYPE_DC_TRANSFER:
1172 pipe = parse_dc_analysis (resources);
1173 dc_enabled = FALSE;
1174 break;
1175 case ANALYSIS_TYPE_AC:
1176 pipe = parse_ac_analysis (resources);
1177 ac_enabled = FALSE;
1178 break;
1179 case ANALYSIS_TYPE_NOISE:
1180 pipe = parse_noise_analysis (resources);
1181 noise_enabled = FALSE;
1182 break;
1183 default:
1184 oregano_warning ("Unexpected analysis found");
1185 unexpected_analysis_found = TRUE;
1186 break;
1187 }
1188
1189 g_mutex_lock(¤t->mutex);
1190 current->type = ANALYSIS_TYPE_NONE;
1191 g_mutex_unlock(¤t->mutex);
1192
1193 if (unexpected_analysis_found || pipe == NULL)
1194 break;
1195 }
1196
1197 if (pipe != NULL)
1198 thread_pipe_set_read_eof(pipe);
1199
1200 resources->pipe = NULL;
1201 }
1202