/* * get_hkl.c * * Small program to manipulate reflection lists * * Copyright © 2013-2021 Deutsches Elektronen-Synchrotron DESY, * a research centre of the Helmholtz Association. * * Authors: * 2009-2021 Thomas White * * 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 . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "version.h" static void show_help(const char *s) { printf("Syntax: %s [options]\n\n", s); printf( "Manipulate reflection lists.\n" "\n" " -h, --help Display this help message.\n" " --version Print CrystFEL version number and exit.\n" "\n" " -i, --input= Read reflections from .\n" " -y, --symmetry= The symmetry of the input reflection list.\n" " -p, --pdb= PDB file with cell parameters (needed when\n" " using a resolution cutoff)\n" "\n" "You can add noise to the reflections with either of:\n" " --poisson Simulate Poisson samples.\n" " --noise Add 10%% random noise.\n" "\n" "To calculate Poisson samples accurately, you must also give:\n" " --adu-per-photon= Number of ADU per photon.\n" "\n" "You can artificially 'twin' the reflections, or expand them out.\n" " -w, --twin= Generate twinned data according to the given\n" " point group.\n" " -e, --expand= Expand reflections to this point group.\n" " --no-need-all-parts Output a twinned reflection even if not all\n" " the necessary equivalents were present.\n" "\n" "You can reindex the reflections according to an operation, e.g. k,h,-l:\n" " --reindex= Reindex according to .\n" "\n" "Use this option with care, and only if you understand why it might sometimes\n" " be necessary:\n" " --trim-centrics Remove reflections which are duplicated in the\n" " point group specified with the '-y' option.\n" "\n" "You can restrict which reflections are written out:\n" " -t, --template= Only include reflections mentioned in file.\n" " --cutoff-angstroms= Only include reflections with d < n Angstroms.\n" " --highres= Synonym for --cutoff-angstroms.\n" " --lowres= Only include reflections with d > n Angstroms.\n" "\n" "You might sometimes need to do this:\n" " --multiplicity Multiply intensities by the number of\n" " equivalent reflections.\n" "\n" "Don't forget to specify the output filename:\n" " -o, --output= Output filename (default: stdout).\n" " --output-format=mtz Output in MTZ format.\n" " --output-format=mtz-bij Output in MTZ format, Bijvoet pairs together\n" " --output-format=xds Output in XDS format.\n" ); } static void copy_notes(RefList *out, RefList *in) { if ( reflist_get_notes(in) != NULL ) { reflist_add_notes(out, reflist_get_notes(in)); } } /* Apply Poisson noise to all reflections */ static void poisson_reflections(RefList *list, double adu_per_photon) { Reflection *refl; RefListIterator *iter; gsl_rng *rng; rng = gsl_rng_alloc(gsl_rng_mt19937); for ( refl = first_refl(list, &iter); refl != NULL; refl = next_refl(refl, iter) ) { double val, c; val = get_intensity(refl); c = adu_per_photon * poisson_noise(rng, val/adu_per_photon); set_intensity(refl, c); } gsl_rng_free(rng); } /* Apply 10% uniform noise to all reflections */ static void noise_reflections(RefList *list) { Reflection *refl; RefListIterator *iter; for ( refl = first_refl(list, &iter); refl != NULL; refl = next_refl(refl, iter) ) { double val, r; val = get_intensity(refl); r = (double)random()/RAND_MAX; val += 0.1 * val * r; set_intensity(refl, val); } } static RefList *template_reflections(RefList *list, RefList *template) { Reflection *refl; RefListIterator *iter; RefList *out; out = reflist_new(); copy_notes(out, list); for ( refl = first_refl(template, &iter); refl != NULL; refl = next_refl(refl, iter) ) { signed int h, k, l; Reflection *new; Reflection *old; get_indices(refl, &h, &k, &l); old = find_refl(list, h, k, l); if ( old == NULL ) continue; new = add_refl(out, h, k, l); copy_data(new, old); } return out; } static RefList *twin_reflections(RefList *in, int need_all_parts, const SymOpList *holo, const SymOpList *mero) { Reflection *refl; RefListIterator *iter; RefList *out; SymOpMask *m; int n; out = reflist_new(); copy_notes(out, in); /* No need to free and reallocate this for every reflection */ m = new_symopmask(holo); for ( refl = first_refl(in, &iter); refl != NULL; refl = next_refl(refl, iter) ) { double total, sigma; int multi; signed int h, k, l; int j; int skip; /* Figure out where to put the twinned version, and check it's * not there already. */ get_indices(refl, &h, &k, &l); get_asymm(holo, h, k, l, &h, &k, &l); if ( find_refl(out, h, k, l) != NULL ) continue; special_position(holo, m, h, k, l); n = num_equivs(holo, m); total = 0.0; sigma = 0.0; multi = 0; skip = 0; for ( j=0; j lowres) ) { Reflection *a; a = add_refl(n, h, k, l); copy_data(a, refl); } } reflist_free(input); return n; } int main(int argc, char *argv[]) { int c; int config_noise = 0; int config_poisson = 0; int config_multi = 0; int config_trimc = 0; int config_nap = 1; char *holo_str = NULL; char *mero_str = NULL; char *sym_str_fromfile = NULL; char *expand_str = NULL; SymOpList *holo = NULL; SymOpList *mero = NULL; SymOpList *expand = NULL; char *input_file = NULL; char *template = NULL; char *output = NULL; RefList *input; double adu_per_photon = 0.0; int have_adu_per_photon = 0; int have_cutoff_aniso = 0; char *cutoff_str = NULL; float cutn1, cutn2, cutn3; char *cellfile = NULL; char *reindex_str = NULL; SymOpList *reindex = NULL; float lowres = 0.0; /* 1/d value */ double highres = INFINITY; /* 1/d value */ UnitCell *cell = NULL; char *output_format_str = NULL; int r; /* Long options */ const struct option longopts[] = { {"help", 0, NULL, 'h'}, {"input", 1, NULL, 'i'}, {"version", 0, NULL, 5 }, {"template", 1, NULL, 't'}, {"poisson", 0, &config_poisson, 1}, {"noise", 0, &config_noise, 1}, {"output", 1, NULL, 'o'}, {"symmetry", 1, NULL, 'y'}, {"twin", 1, NULL, 'w'}, {"expand", 1, NULL, 'e'}, {"intensities", 1, NULL, 'i'}, {"pdb", 1, NULL, 'p'}, {"multiplicity", 0, &config_multi, 1}, {"trim-centrics", 0, &config_trimc, 1}, {"no-need-all-parts", 0, &config_nap, 0}, {"adu-per-photon", 1, NULL, 2}, {"cutoff-angstroms", 1, NULL, 3}, {"highres", 1, NULL, 3}, {"reindex", 1, NULL, 4}, {"lowres", 1, NULL, 6}, {"output-format", 1, NULL, 7}, {0, 0, NULL, 0} }; /* Short options */ while ((c = getopt_long(argc, argv, "ht:o:i:w:y:e:p:", longopts, NULL)) != -1) { switch (c) { case 'h' : show_help(argv[0]); return 0; case 5 : printf("CrystFEL: %s\n", crystfel_version_string()); printf("%s\n", crystfel_licence_string()); return 0; case 't' : template = strdup(optarg); break; case 'o' : output = strdup(optarg); break; case 'i' : input_file = strdup(optarg); break; case 'y' : mero_str = strdup(optarg); break; case 'w' : holo_str = strdup(optarg); break; case 'e' : expand_str = strdup(optarg); break; case 'p' : cellfile = strdup(optarg); break; case 2 : adu_per_photon = strtof(optarg, NULL); have_adu_per_photon = 1; break; case 3 : cutoff_str = strdup(optarg); break; case 4 : reindex_str = strdup(optarg); break; case 6 : if (sscanf(optarg, "%f", &lowres) != 1) { ERROR("Invalid value for --lowres\n"); return 1; } lowres /= 1e10; /* Angstroms -> m */ lowres = 1.0/lowres; /* m -> m^-1 */ break; case 7 : output_format_str = strdup(optarg); break; case 0 : break; case '?' : break; default : ERROR("Unhandled option '%c'\n", c); break; } } if ( cutoff_str != NULL ) { int r; r = sscanf(cutoff_str, "%f,%f,%f", &cutn1, &cutn2, &cutn3); if ( r == 3 ) { have_cutoff_aniso = 1; /* Convert Angstroms -> m */ cutn1 /= 1e10; cutn2 /= 1e10; cutn3 /= 1e10; /* Convert m -> m^-1 */ cutn1 = 1.0/cutn1; cutn2 = 1.0/cutn2; cutn3 = 1.0/cutn3; /* Set isotropic cutoff to the largest */ highres = biggest(cutn1, biggest(cutn2, cutn3)); } else { char *rval; errno = 0; /* highres is in m */ highres = strtod(cutoff_str, &rval); /* Convert Angstroms -> m -> m^-1 */ highres /= 1e10; highres = 1.0/highres; if ( *rval != '\0' ) { ERROR("Invalid value for --cutoff-angstroms.\n"); return 1; } } free(cutoff_str); } if ( (holo_str != NULL) && (expand_str != NULL) ) { ERROR("You cannot 'twin' and 'expand' at the same time.\n"); ERROR("Decide which one you want to do first.\n"); return 1; } if ( holo_str != NULL ) { pointgroup_warning(holo_str); holo = get_pointgroup(holo_str); free(holo_str); } else { holo = NULL; } if ( expand_str != NULL ) { pointgroup_warning(expand_str); expand = get_pointgroup(expand_str); free(expand_str); } else { expand = NULL; } if ( reindex_str != NULL ) { reindex = parse_symmetry_operations(reindex_str); if ( reindex == NULL ) return 1; if ( num_equivs(reindex, NULL) != 1 ) { ERROR("Please provide only ONE reindexing operation\n"); return 1; } set_symmetry_name(reindex, "Reindex"); } input = read_reflections_2(input_file, &sym_str_fromfile); if ( input == NULL ) { ERROR("Problem reading input file %s\n", input_file); return 1; } free(input_file); if ( mero_str == NULL ) { if ( sym_str_fromfile != NULL ) { STATUS("Using symmetry from reflection file: %s\n", sym_str_fromfile); mero_str = sym_str_fromfile; } else { mero_str = strdup("1"); } } if ( mero_str != NULL ) { pointgroup_warning(mero_str); mero = get_pointgroup(mero_str); free(mero_str); } else { mero = NULL; } if ( (expand != NULL) || (holo != NULL) || config_trimc || config_multi ) { if ( mero == NULL ) { ERROR("You must specify the point group with -y.\n"); } } STATUS("%i reflections in input.\n", num_reflections(input)); if ( (mero != NULL) && !config_trimc && check_list_symmetry(input, mero) ) { ERROR("The input reflection list does not appear to" " have symmetry %s\n", symmetry_name(mero)); ERROR("If your unit cell is monoclinic, you may need to specify" " the unique axis for your point group. The default is" " unique axis c.\n"); ERROR("See 'man crystfel' for more details.\n"); return 1; } if ( config_poisson ) { if ( have_adu_per_photon ) { poisson_reflections(input, adu_per_photon); } else { ERROR("You must give the number of ADU per photon to " "use --poisson.\n"); return 1; } } if ( config_noise ) noise_reflections(input); if ( holo != NULL ) { RefList *new; STATUS("Twinning from %s into %s\n", symmetry_name(mero), symmetry_name(holo)); new = twin_reflections(input, config_nap, holo, mero); /* Replace old with new */ reflist_free(input); input = new; /* The symmetry of the list has changed */ free(mero); mero = holo; } if ( expand != NULL ) { RefList *new; STATUS("Expanding from %s into %s\n", symmetry_name(mero), symmetry_name(expand)); new = expand_reflections(input, mero, expand); /* Replace old with new */ reflist_free(input); input = new; mero = expand; } if ( config_trimc ) { RefList *new; /* Can't do this if point group is invalid */ if ( mero == NULL ) { ERROR("Need point group to trim centrics.\n"); return 1; } STATUS("Trimming duplicate reflections in %s\n", symmetry_name(mero)); new = trim_centrics(input, mero); reflist_free(input); input = new; STATUS("%i output reflections\n", num_reflections(input)); } if ( config_multi ) { Reflection *refl; RefListIterator *iter; SymOpMask *m; m = new_symopmask(mero); for ( refl = first_refl(input, &iter); refl != NULL; refl = next_refl(refl, iter) ) { double inty; signed int h, k, l; get_indices(refl, &h, &k, &l); inty = get_intensity(refl); special_position(mero, m, h, k, l); inty *= (double)num_equivs(mero, m); set_intensity(refl, inty); } free_symopmask(m); } if ( template ) { RefList *t = read_reflections(template); RefList *new = template_reflections(input, t); reflist_free(input); input = new; } if ( cellfile != NULL ) { cell = load_cell_from_file(cellfile); if ( cell == NULL ) { ERROR("Failed to load cell from '%s'\n", cellfile); return 1; } free(cellfile); } if ( (highres < INFINITY) || (lowres > 0.0) ) { if ( cell == NULL ) { ERROR("You must provide a unit cell when using " "--highres or --lowres\n"); return 1; } else { input = apply_resolution_cutoff(input, lowres, highres, cell); } } if ( have_cutoff_aniso ) { RefList *n; Reflection *refl; RefListIterator *iter; double asx, asy, asz; double bsx, bsy, bsz; double csx, csy, csz; double as, bs, cs; if ( cell == NULL ) { ERROR("You must provide a unit cell when using " "--cutoff-angstroms\n"); return 1; } cell_get_reciprocal(cell, &asx, &asy, &asz, &bsx, &bsy, &bsz, &csx, &csy, &csz); as = modulus(asx, asy, asz); bs = modulus(bsx, bsy, bsz); cs = modulus(csx, csy, csz); n = reflist_new(); copy_notes(n, input); for ( refl = first_refl(input, &iter); refl != NULL; refl = next_refl(refl, iter) ) { signed int h, k, l; double sum; get_indices(refl, &h, &k, &l); sum = pow(h*as/cutn1, 2.0); sum += pow(k*bs/cutn2, 2.0); sum += pow(l*cs/cutn3, 2.0); if ( sum < 1.0 ) { Reflection *a; a = add_refl(n, h, k, l); copy_data(a, refl); } } cell_free(cell); reflist_free(input); input = n; } if ( reindex != NULL ) { RefList *n; Reflection *refl; RefListIterator *iter; n = reflist_new(); copy_notes(n, input); for ( refl = first_refl(input, &iter); refl != NULL; refl = next_refl(refl, iter) ) { signed int h, k, l; Reflection *rn; get_indices(refl, &h, &k, &l); get_equiv(reindex, NULL, 0, h, k, l, &h, &k, &l); rn = add_refl(n, h, k, l); copy_data(rn, refl); } reflist_free(input); input = n; } reflist_add_command_and_version(input, argc, argv); /* Yes, really! */ if ( output_format_str == NULL ) { r = write_reflist_2(output, input, mero); } else if ( cell == NULL ) { ERROR("You must provide a unit cell to use MTZ or XDS output.\n"); r = 1; } else if ( strcasecmp(output_format_str, "mtz") == 0 ) { if ( !libcrystfel_can_write_mtz() ) { ERROR("Sorry, this version of CrystFEL was compiled " "without MTZ support (libccp4 is required)\n"); r = 1; } else if ( output == NULL ) { ERROR("You must provide the MTZ output filename.\n"); r = 1; } else { r = write_to_mtz(input, mero, cell, 0, INFINITY, output, "dataset", "crystal", "project", 0); } } else if ( strcasecmp(output_format_str, "mtz-bij") == 0 ) { if ( !libcrystfel_can_write_mtz() ) { ERROR("Sorry, this version of CrystFEL was compiled " "without MTZ support (libccp4 is required)\n"); r = 1; } else if ( output == NULL ) { ERROR("You must provide the MTZ output filename.\n"); r = 1; } else { r = write_to_mtz(input, mero, cell, 0, INFINITY, output, "dataset", "crystal", "project", 1); } } else if ( strcasecmp(output_format_str, "xds") == 0 ) { if ( output == NULL ) { ERROR("You must provide the XDS output filename.\n"); r = 1; } else { r = write_to_xds(input, mero, cell, 0, INFINITY, output); } } else { STATUS("Unrecognised output format '%s'\n", output_format_str); r = 1; } if ( r ) { ERROR("Failed to write output file.\n"); } free(output); free_symoplist(mero); reflist_free(input); return r; }