pattern_sim.c 15.9 KB
Newer Older
1
/*
Thomas White's avatar
Thomas White committed
2
3
4
 * pattern_sim.c
 *
 * Simulate diffraction patterns from small crystals
5
 *
Thomas White's avatar
Thomas White committed
6
 * Copyright © 2012 Thomas White <taw@physics.org>
7
 *
Thomas White's avatar
Thomas White committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 * This file is part of CrystFEL.
 *
 * CrystFEL 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 3 of the License, or
 * (at your option) any later version.
 *
 * CrystFEL 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
 * along with CrystFEL.  If not, see <http://www.gnu.org/licenses/>.
22
23
24
25
26
27
28
29
30
31
32
33
34
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
Thomas White's avatar
Thomas White committed
35
#include <getopt.h>
36

37
#include "image.h"
Thomas White's avatar
Thomas White committed
38
#include "diffraction.h"
39
#include "diffraction-gpu.h"
40
#include "cell.h"
41
42
#include "utils.h"
#include "hdf5-file.h"
Thomas White's avatar
Thomas White committed
43
#include "detector.h"
44
#include "peaks.h"
45
#include "beam-parameters.h"
Thomas White's avatar
Thomas White committed
46
#include "symmetry.h"
Thomas White's avatar
Thomas White committed
47
48
#include "reflist.h"
#include "reflist-utils.h"
49
#include "pattern_sim.h"
50
51


Thomas White's avatar
Thomas White committed
52
static void show_help(const char *s)
53
{
54
	printf("Syntax: %s [options]\n\n", s);
Thomas White's avatar
Thomas White committed
55
	printf(
Thomas White's avatar
Thomas White committed
56
"Simulate diffraction patterns from small crystals probed with femtosecond\n"
Thomas White's avatar
Thomas White committed
57
58
"pulses of X-rays from a free electron laser.\n"
"\n"
59
" -h, --help                Display this help message.\n"
Thomas White's avatar
Thomas White committed
60
"\n"
61
62
63
" -p, --pdb=<file>          PDB file from which to get the unit cell.\n"
"                            (The actual Bragg intensities come from the\n"
"                            intensities file)\n"
64
"     --gpu                 Use the GPU to speed up the calculation.\n"
Thomas White's avatar
Thomas White committed
65
66
"     --gpu-dev=<n>         Use GPU device <n>.  Omit this option to see the\n"
"                            available devices.\n"
67
68
" -g, --geometry=<file>     Get detector geometry from file.\n"
" -b, --beam=<file>         Get beam parameters from file.\n"
69
70
71
"\n"
" -n, --number=<N>          Generate N images.  Default 1.\n"
"     --no-images           Do not output any HDF5 files.\n"
72
73
74
75
76
77
" -o, --output=<filename>   Output HDF5 filename.  Default: sim.h5.\n"
"                            If you choose to simulate more than one pattern,\n"
"                            the filename given will be postfixed with a\n"
"                            hyphen, the image number and '.h5'.  In this\n"
"                            case, the default value is 'sim', such that the\n"
"                            files produced are sim-1.h5, sim-2.h5 and so on.\n"
Thomas White's avatar
Thomas White committed
78
" -r, --random-orientation  Use a randomly generated orientation\n"
79
"                            (a new orientation will be used for each image).\n"
80
81
"     --powder=<file>       Write a summed pattern of all images simulated by\n"
"                            this invocation as the given filename.\n"
82
" -i, --intensities=<file>  Specify file containing reflection intensities\n"
Thomas White's avatar
Thomas White committed
83
"                            (and phases) to use.\n"
84
" -y, --symmetry=<sym>      The symmetry of the intensities file.\n"
Thomas White's avatar
Thomas White committed
85
" -t, --gradients=<method>  Use <method> for the calculation of shape\n"
86
87
"                            transform intensities.  Choose from:\n"
"                             mosaic      : Take the intensity of the nearest\n"
Thomas White's avatar
Thomas White committed
88
89
90
"                                           Bragg position.  This is the\n"
"                                           fastest method and the only one\n"
"                                           supported on the GPU.\n"
91
92
"                             interpolate : Interpolate trilinearly between\n"
"                                           six adjacent Bragg intensities.\n"
Thomas White's avatar
Thomas White committed
93
94
"                                           This method has intermediate\n"
"                                           accuracy.\n"
95
"                             phased      : As 'interpolate', but take phase\n"
Thomas White's avatar
Thomas White committed
96
97
98
"                                           values into account.  This is the\n"
"                                           most accurate method, but the\n"
"                                           slowest.\n"
99
100
101
"     --really-random       Use really random numbers for the orientation and\n"
"                            crystal size.  By default, the same sequence\n"
"                            will be used for each new run.\n"
102
103
104
105
106
"     --min-size=<s>        Generate random crystal sizes using <s> as the\n"
"                            minimum crystal size in nm. --max-size is also\n"
"                            required.\n"
"     --max-size=<s>        Use <s> as the maximum crystal size in nm.\n"
"                            --min-size is also required.\n"
Thomas White's avatar
Thomas White committed
107
"     --no-noise            Do not calculate Poisson noise.\n"
108
);
Thomas White's avatar
Thomas White committed
109
110
111
}


112
113
114
115
static double *intensities_from_list(RefList *list)
{
	Reflection *refl;
	RefListIterator *iter;
116
	double *out = new_arr_intensity();
117
118
119
120
121
122
123
124
125
126

	for ( refl = first_refl(list, &iter);
	      refl != NULL;
	      refl = next_refl(refl, iter) ) {

		signed int h, k, l;
		double intensity = get_intensity(refl);

		get_indices(refl, &h, &k, &l);

127
		set_arr_intensity(out, h, k, l, intensity);
128
129
130
131
132
133
134
135
136
137
138

	}

	return out;
}


static double *phases_from_list(RefList *list)
{
	Reflection *refl;
	RefListIterator *iter;
139
	double *out = new_arr_phase();
140
141
142
143
144
145
146
147
148
149

	for ( refl = first_refl(list, &iter);
	      refl != NULL;
	      refl = next_refl(refl, iter) ) {

		signed int h, k, l;
		double phase = get_phase(refl, NULL);

		get_indices(refl, &h, &k, &l);

150
		set_arr_phase(out, h, k, l, phase);
151
152
153
154
155
156
157
158
159
160
161
162

	}

	return out;

}


static unsigned char *flags_from_list(RefList *list)
{
	Reflection *refl;
	RefListIterator *iter;
163
	unsigned char *out = new_arr_flag();
164
165
166
167
168
169
170
171
172

	for ( refl = first_refl(list, &iter);
	      refl != NULL;
	      refl = next_refl(refl, iter) ) {

		signed int h, k, l;

		get_indices(refl, &h, &k, &l);

173
		set_arr_flag(out, h, k, l, 1);
174
175
176
177
178
179
180
181

	}

	return out;

}


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
static struct quaternion read_quaternion()
{
	do {

		int r;
		float w, x, y, z;
		char line[1024];
		char *rval;

		printf("Please input quaternion: w x y z\n");
		rval = fgets(line, 1023, stdin);
		if ( rval == NULL ) return invalid_quaternion();
		chomp(line);

		r = sscanf(line, "%f %f %f %f", &w, &x, &y, &z);
		if ( r == 4 ) {

			struct quaternion quat;

			quat.w = w;
			quat.x = x;
			quat.y = y;
			quat.z = z;

			return quat;

		} else {
209
			ERROR("Invalid rotation '%s'\n", line);
210
211
212
		}

	} while ( 1 );
213
214
215
}


216
217
218
219
220
221
222
223
224
225
226
227
static int random_ncells(double len, double min_nm, double max_nm)
{
	int min_cells, max_cells, wid;

	min_cells = min_nm*1e-9 / len;
	max_cells = max_nm*1e-9 / len;
	wid = max_cells - min_cells;

	return wid*random()/RAND_MAX + min_cells;
}


228
229
int main(int argc, char *argv[])
{
230
	int c;
231
	struct image image;
232
	struct gpu_context *gctx = NULL;
233
	double *powder;
234
235
	char *intfile = NULL;
	double *intensities;
236
	char *rval;
237
	double *phases;
238
	unsigned char *flags;
239
	int config_randomquat = 0;
240
	int config_noimages = 0;
Thomas White's avatar
Thomas White committed
241
	int config_nonoise = 0;
242
	int config_nosfac = 0;
243
	int config_gpu = 0;
244
	int config_random = 0;
245
	char *powder_fn = NULL;
246
	char *filename = NULL;
247
	char *grad_str = NULL;
248
	char *outfile = NULL;
Thomas White's avatar
Thomas White committed
249
	char *geometry = NULL;
250
	char *beamfile = NULL;
251
	GradientMethod grad;
Thomas White's avatar
Thomas White committed
252
253
254
	int ndone = 0;    /* Number of simulations done (images or not) */
	int number = 1;   /* Number used for filename of image */
	int n_images = 1; /* Generate one image by default */
255
	int done = 0;
Thomas White's avatar
Thomas White committed
256
257
	UnitCell *input_cell;
	struct quaternion orientation;
Thomas White's avatar
Thomas White committed
258
	int gpu_dev = -1;
259
260
261
	int random_size = 0;
	double min_size = 0.0;
	double max_size = 0.0;
Thomas White's avatar
Thomas White committed
262
263
	char *sym_str = NULL;
	SymOpList *sym;
Thomas White's avatar
Thomas White committed
264

265
	/* Long options */
Thomas White's avatar
Thomas White committed
266
	const struct option longopts[] = {
267
		{"help",               0, NULL,               'h'},
268
		{"gpu",                0, &config_gpu,         1},
269
270
		{"random-orientation", 0, NULL,               'r'},
		{"number",             1, NULL,               'n'},
Thomas White's avatar
Thomas White committed
271
		{"no-images",          0, &config_noimages,    1},
Thomas White's avatar
Thomas White committed
272
		{"no-noise",           0, &config_nonoise,     1},
273
		{"intensities",        1, NULL,               'i'},
274
		{"symmetry",           1, NULL,               'y'},
275
		{"powder",             1, NULL,               'w'},
Thomas White's avatar
Thomas White committed
276
		{"gradients",          1, NULL,               't'},
277
		{"pdb",                1, NULL,               'p'},
278
		{"output",             1, NULL,               'o'},
Thomas White's avatar
Thomas White committed
279
		{"geometry",           1, NULL,               'g'},
280
		{"beam",               1, NULL,               'b'},
281
		{"really-random",      0, &config_random,      1},
Thomas White's avatar
Thomas White committed
282
		{"gpu-dev",            1, NULL,                2},
283
284
		{"min-size",           1, NULL,                3},
		{"max-size",           1, NULL,                4},
285
		{0, 0, NULL, 0}
Thomas White's avatar
Thomas White committed
286
	};
287

288
	/* Short options */
289
	while ((c = getopt_long(argc, argv, "hrn:i:t:p:o:g:b:y:",
Thomas White's avatar
Thomas White committed
290
	                        longopts, NULL)) != -1) {
291

Thomas White's avatar
Thomas White committed
292
		switch (c) {
Thomas White's avatar
Thomas White committed
293
		case 'h' :
Thomas White's avatar
Thomas White committed
294
295
			show_help(argv[0]);
			return 0;
296

Thomas White's avatar
Thomas White committed
297
		case 'r' :
298
299
300
			config_randomquat = 1;
			break;

Thomas White's avatar
Thomas White committed
301
		case 'n' :
302
303
304
305
306
			n_images = strtol(optarg, &rval, 10);
			if ( *rval != '\0' ) {
				ERROR("Invalid number of images.\n");
				return 1;
			}
307
308
			break;

Thomas White's avatar
Thomas White committed
309
		case 'i' :
310
311
312
			intfile = strdup(optarg);
			break;

Thomas White's avatar
Thomas White committed
313
		case 't' :
314
315
316
			grad_str = strdup(optarg);
			break;

Thomas White's avatar
Thomas White committed
317
		case 'p' :
318
319
320
			filename = strdup(optarg);
			break;

Thomas White's avatar
Thomas White committed
321
		case 'o' :
322
323
324
			outfile = strdup(optarg);
			break;

Thomas White's avatar
Thomas White committed
325
		case 'w' :
326
327
328
			powder_fn = strdup(optarg);
			break;

Thomas White's avatar
Thomas White committed
329
		case 'g' :
Thomas White's avatar
Thomas White committed
330
331
332
			geometry = strdup(optarg);
			break;

333
334
335
336
		case 'b' :
			beamfile = strdup(optarg);
			break;

337
		case 'y' :
Thomas White's avatar
Thomas White committed
338
			sym_str = strdup(optarg);
339
340
			break;

Thomas White's avatar
Thomas White committed
341
342
343
344
		case 2 :
			gpu_dev = atoi(optarg);
			break;

345
		case 3 :
346
347
348
349
350
			min_size = strtod(optarg, &rval);
			if ( *rval != '\0' ) {
				ERROR("Invalid minimum size.\n");
				return 1;
			}
351
352
353
354
			random_size++;
			break;

		case 4 :
355
356
357
358
359
			max_size = strtod(optarg, &rval);
			if ( *rval != '\0' ) {
				ERROR("Invalid maximum size.\n");
				return 1;
			}
360
361
362
			random_size++;
			break;

Thomas White's avatar
Thomas White committed
363
		case 0 :
Thomas White's avatar
Thomas White committed
364
			break;
365

Thomas White's avatar
Thomas White committed
366
		default :
Thomas White's avatar
Thomas White committed
367
368
			return 1;
		}
369
370
371

	}

372
	if ( config_random ) {
373
374
375
376
377
378
379
380
		FILE *fh;
		unsigned int seed;
		fh = fopen("/dev/urandom", "r");
		fread(&seed, sizeof(seed), 1, fh);
		fclose(fh);
		srand(seed);
	}

381
382
383
384
385
	if ( random_size == 1 ) {
		ERROR("You must specify both --min-size and --max-size.\n");
		return 1;
	}

386
387
388
389
	if ( filename == NULL ) {
		filename = strdup("molecule.pdb");
	}

390
391
392
393
394
395
396
397
	if ( outfile == NULL ) {
		if ( n_images == 1 ) {
			outfile = strdup("sim.h5");
		} else {
			outfile = strdup("sim");
		}
	}

Thomas White's avatar
Thomas White committed
398
399
400
	if ( sym_str == NULL ) sym_str = strdup("1");
	sym = get_pointgroup(sym_str);
	/* sym_str is used below */
401

402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
	if ( grad_str == NULL ) {
		STATUS("You didn't specify a gradient calculation method, so"
		       " I'm using the 'mosaic' method, which is fastest.\n");
		grad = GRADIENT_MOSAIC;
	} else if ( strcmp(grad_str, "mosaic") == 0 ) {
		grad = GRADIENT_MOSAIC;
	} else if ( strcmp(grad_str, "interpolate") == 0) {
		grad = GRADIENT_INTERPOLATE;
	} else if ( strcmp(grad_str, "phased") == 0) {
		grad = GRADIENT_PHASED;
	} else {
		ERROR("Unrecognised gradient method '%s'\n", grad_str);
		return 1;
	}
	free(grad_str);

	if ( config_gpu && (grad != GRADIENT_MOSAIC) ) {
		ERROR("Only the mosaic method can be used for gradients when"
		      "calculating on the GPU.\n");
		return 1;
	}

Thomas White's avatar
Thomas White committed
424
425
426
427
428
	if ( geometry == NULL ) {
		ERROR("You need to specify a geometry file with --geometry\n");
		return 1;
	}

429
430
431
432
433
434
	if ( beamfile == NULL ) {
		ERROR("You need to specify a beam parameter file"
		      " with --beam\n");
		return 1;
	}

435
	if ( intfile == NULL ) {
436

437
438
439
440
441
442
		/* Gentle reminder */
		STATUS("You didn't specify the file containing the ");
		STATUS("reflection intensities (with --intensities).\n");
		STATUS("I'll simulate a flat intensity distribution.\n");
		intensities = NULL;
		phases = NULL;
443
444
		flags = NULL;

445
	} else {
446

Thomas White's avatar
Thomas White committed
447
448
449
		RefList *reflections;

		reflections = read_reflections(intfile);
Thomas White's avatar
Thomas White committed
450
451
452
453
		if ( reflections == NULL ) {
			ERROR("Problem reading input file %s\n", intfile);
			return 1;
		}
Thomas White's avatar
Thomas White committed
454
		free(intfile);
455

456
		if ( grad == GRADIENT_PHASED ) {
Thomas White's avatar
Thomas White committed
457
			phases = phases_from_list(reflections);
458
459
460
		} else {
			phases = NULL;
		}
Thomas White's avatar
Thomas White committed
461
462
463
		intensities = intensities_from_list(reflections);
		phases = phases_from_list(reflections);
		flags = flags_from_list(reflections);
464

Thomas White's avatar
Thomas White committed
465
		/* Check that the intensities have the correct symmetry */
Thomas White's avatar
Thomas White committed
466
		if ( check_list_symmetry(reflections, sym) ) {
Thomas White's avatar
Thomas White committed
467
			ERROR("The input reflection list does not appear to"
Thomas White's avatar
Thomas White committed
468
			      " have symmetry %s\n", symmetry_name(sym));
Thomas White's avatar
Thomas White committed
469
470
471
			return 1;
		}

Thomas White's avatar
Thomas White committed
472
473
		reflist_free(reflections);

474
475
	}

476
477
478
479
480
481
482
	image.det = get_detector_geometry(geometry);
	if ( image.det == NULL ) {
		ERROR("Failed to read detector geometry from '%s'\n", geometry);
		return 1;
	}
	free(geometry);

483
484
485
486
487
488
489
	image.beam = get_beam_parameters(beamfile);
	if ( image.beam == NULL ) {
		ERROR("Failed to read beam parameters from '%s'\n", beamfile);
		return 1;
	}
	free(beamfile);

Thomas White's avatar
Thomas White committed
490
	/* Define image parameters */
Thomas White's avatar
Thomas White committed
491
492
	image.width = image.det->max_fs + 1;
	image.height = image.det->max_ss + 1;
493
	image.lambda = ph_en_to_lambda(eV_to_J(image.beam->photon_energy));
Thomas White's avatar
Thomas White committed
494
495
496
497

	/* Load unit cell */
	input_cell = load_cell_from_pdb(filename);
	if ( input_cell == NULL ) {
Thomas White's avatar
Thomas White committed
498
499
		exit(1);
	}
Thomas White's avatar
Thomas White committed
500
501

	/* Initialise stuff */
502
	image.filename = NULL;
Thomas White's avatar
Thomas White committed
503
504
	image.features = NULL;
	image.flags = NULL;
Thomas White's avatar
Thomas White committed
505

506
507
	powder = calloc(image.width*image.height, sizeof(*powder));

Thomas White's avatar
Thomas White committed
508
	/* Splurge a few useful numbers */
509
	STATUS("Wavelength is %f nm\n", image.lambda/1.0e-9);
Thomas White's avatar
Thomas White committed
510

511
512
	do {

Thomas White's avatar
Thomas White committed
513
		int na, nb, nc;
514
		double a, b, c, d;
Thomas White's avatar
Thomas White committed
515
		UnitCell *cell;
Thomas White's avatar
Thomas White committed
516

517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
		if ( random_size ) {

			double alen, blen, clen, dis;

			cell_get_parameters(input_cell, &alen, &blen, &clen,
			                    &dis, &dis, &dis);

			na = random_ncells(alen, min_size, max_size);
			nb = random_ncells(blen, min_size, max_size);
			nc = random_ncells(clen, min_size, max_size);

		} else {

			na = 8;
			nb = 8;
			nc = 8;

		}
Thomas White's avatar
Thomas White committed
535

536
537
		/* Read quaternion from stdin */
		if ( config_randomquat ) {
Thomas White's avatar
Thomas White committed
538
			orientation = random_quaternion();
539
		} else {
Thomas White's avatar
Thomas White committed
540
			orientation = read_quaternion();
541
542
		}

Thomas White's avatar
Thomas White committed
543
		STATUS("Orientation is %5.3f %5.3f %5.3f %5.3f\n",
Thomas White's avatar
Thomas White committed
544
545
		       orientation.w, orientation.x,
		       orientation.y, orientation.z);
Thomas White's avatar
Thomas White committed
546

Thomas White's avatar
Thomas White committed
547
		if ( !quaternion_valid(orientation) ) {
548
			ERROR("Orientation modulus is not zero!\n");
549
550
551
			return 1;
		}

Thomas White's avatar
Thomas White committed
552
553
		cell = cell_rotate(input_cell, orientation);

554
555
556
		/* Ensure no residual information */
		image.data = NULL;
		image.twotheta = NULL;
557

558
		cell_get_parameters(cell, &a, &b, &c, &d, &d, &d);
Thomas White's avatar
Thomas White committed
559
560
		STATUS("Particle size = %i x %i x %i"
		       " ( = %5.2f x %5.2f x %5.2f nm)\n",
561
562
	               na, nb, nc, na*a/1.0e-9, nb*b/1.0e-9, nc*c/1.0e-9);

563
		if ( config_gpu ) {
564
			if ( gctx == NULL ) {
565
				gctx = setup_gpu(config_nosfac,
Thomas White's avatar
Thomas White committed
566
				                 intensities, flags, sym_str,
567
				                 gpu_dev);
568
			}
569
			get_diffraction_gpu(gctx, &image, na, nb, nc, cell);
570
		} else {
Thomas White's avatar
Thomas White committed
571
			get_diffraction(&image, na, nb, nc, intensities, phases,
Thomas White's avatar
Thomas White committed
572
			                flags, cell, grad, sym);
573
		}
574
		if ( image.data == NULL ) {
575
576
577
578
			ERROR("Diffraction calculation failed.\n");
			goto skip;
		}

579
		record_image(&image, !config_nonoise);
Thomas White's avatar
Thomas White committed
580

581
		if ( powder_fn != NULL ) {
582
583
584
585
586
587
588

			int x, y, w;

			w = image.width;

			for ( x=0; x<image.width; x++ ) {
			for ( y=0; y<image.height; y++ ) {
589
				powder[x+w*y] += (double)image.data[x+w*y];
590
591
592
593
			}
			}

			if ( !(ndone % 10) ) {
594
				hdf5_write(powder_fn, powder,
595
				           image.width, image.height,
596
				           H5T_NATIVE_DOUBLE);
597
598
599
			}
		}

600
601
602
603
		if ( !config_noimages ) {

			char filename[1024];

604
605
606
607
608
609
610
			if ( n_images != 1 ) {
				snprintf(filename, 1023, "%s-%i.h5",
				         outfile, number);
			} else {
				strncpy(filename, outfile, 1023);
			}

611
			number++;
Thomas White's avatar
Thomas White committed
612

613
614
			/* Write the output file */
			hdf5_write(filename, image.data,
615
			           image.width, image.height, H5T_NATIVE_FLOAT);
616
617

		}
618

619
620
621
		/* Clean up */
		free(image.data);
		free(image.twotheta);
Thomas White's avatar
Thomas White committed
622
		cell_free(cell);
Thomas White's avatar
Thomas White committed
623

624
skip:
Thomas White's avatar
Thomas White committed
625
		ndone++;
626

627
		if ( n_images && (ndone >= n_images) ) done = 1;
Thomas White's avatar
Thomas White committed
628

629
	} while ( !done );
630

631
632
633
634
635
636
	if ( powder_fn != NULL ) {
		hdf5_write(powder_fn, powder,
		           image.width, image.height,
		           H5T_NATIVE_DOUBLE);
	}

637
638
639
640
	if ( gctx != NULL ) {
		cleanup_gpu(gctx);
	}

Thomas White's avatar
Thomas White committed
641
642
	free(image.det->panels);
	free(image.det);
643
	free(image.beam);
644
	free(powder);
Thomas White's avatar
Thomas White committed
645
	cell_free(input_cell);
646
	free(intensities);
Thomas White's avatar
Thomas White committed
647
648
	free(outfile);
	free(filename);
Thomas White's avatar
Thomas White committed
649
650
	free(sym_str);
	free_symoplist(sym);
651

652
	return 0;
653
}