/* * check_hkl.c * * Characterise reflection lists * * Copyright © 2012-2021 Deutsches Elektronen-Synchrotron DESY, * a research centre of the Helmholtz Association. * * Authors: * 2010-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 #include #include #include "version.h" static void show_help(const char *s) { printf("Syntax: %s [options] \n\n", s); printf( "Characterise an intensity list.\n" "\n" " -h, --help Display this help message.\n" " --version Print CrystFEL version number and exit.\n" " -y, --symmetry= The symmetry of the input file.\n" " -p, --pdb= Unit cell file to use (PDB or CrystFEL format).\n" " --rmin= Low resolution cutoff (1/d in m^-1).\n" " --rmax= High resolution cutoff (1/d in m^-1).\n" " --lowres= Low resolution cutoff in (d in A).\n" " --highres= High resolution cutoff in (d in A).\n" " --sigma-cutoff= Discard reflections with I/sigma(I) < n.\n" " --nshells= Use resolution shells or bins.\n" " --wilson Calculate a Wilson plot\n" " --ltest Perform an L-test (for twinning)\n" " --shell-file= Write results table to .\n" " --ignore-negs Ignore reflections with negative intensities.\n" " --zero-negs Set negative intensities to zero.\n" "\n"); } static int add_ltest(RefList *list, double i1, int *bins, int nbins, double step, const SymOpList *sym, double *lt, double *l2t, signed int h1, signed int k1, signed int l1, signed int h2, signed int k2, signed int l2) { Reflection *refl; double i2, L; int bin; if ( SERIAL(h1, k1, l1) > SERIAL(h2, k2, l2) ) return 0; refl = find_refl(list, h2, k2, l2); if ( refl == NULL ) { signed int h, k, l; if ( !find_equiv_in_list(list, h2, k2, l2, sym, &h, &k, &l) ) { return 0; } refl = find_refl(list, h, k, l); } i2 = get_intensity(refl); L = (i1-i2) / (i1+i2); if ( isnan(L) ) { /* This happens with --zero-negs and two negative intensities, * because L=(0-0)/(0+0) */ return 0; } bin = fabs(L)/step; if ( (bin < 0) || (isnan(L)) ) { bin = 0; } else if ( bin >= nbins ) { bin = nbins-1; } bins[bin]++; *lt += fabs(L); *l2t += pow(L, 2.0); return 1; } static void l_test(RefList *list, UnitCell *cell, const SymOpList *sym, double rmin_fix, double rmax_fix, int nbins, const char *filename) { Reflection *refl; RefListIterator *iter; int *bins; FILE *fh; int npairs, i; double tot; double lt = 0.0; double l2t = 0.0; const double step = 1.0/nbins; int hd, kd, ld; const char cen = cell_get_centering(cell); bins = malloc(nbins*sizeof(int)); if ( bins == NULL ) return; for ( i=0; i = %.3f (ideal untwinned %.3f, twinned %.3f)\n", lt/npairs, 1.0/2.0, 3.0/8.0); STATUS(" = %.3f (ideal untwinned %.3f, twinned %.3f)\n", l2t/npairs, 1.0/3.0, 1.0/5.0); fh = fopen(filename, "w"); if ( fh == NULL ) return; tot = 0.0; fprintf(fh, " |L| N(|L|) untwinned twinned\n"); fprintf(fh, "%.3f %7.3f %9.3f %7.3f\n", 0.0, tot, 0.0, 0.0); for ( i=0; i 0.0 ) rmin = rmin_fix; if ( rmax_fix > 0.0 ) rmax = rmax_fix; s2min = pow(rmin/2.0, 2.0); s2max = pow(rmax/2.0, 2.0); s2step = (s2max - s2min)/nbins; plot_i = malloc(nbins*sizeof(double)); if ( plot_i == NULL ) return; for ( i=0; i/eE nrefl\n"); for ( i=0; i 0 ) { ERROR("%i bins contained invalid values " "and were ignored.\n", ndisc); } if ( nbfit < 3 ) { ERROR("Not enough bins left.\n"); return; } gsl_fit_linear(s2fit, 1, lnifit, 1, nbfit, &lnk, &minus2B, &cov00, &cov01, &cov11, &sumsq); B = -minus2B/2.0; STATUS("ln k = %.2f\n", lnk); STATUS("B = %.2f A^2\n", B*1e20); } } free(plot_i); free(plot_n); } static void plot_shells(RefList *list, UnitCell *cell, const SymOpList *sym, double rmin_fix, double rmax_fix, int nshells, const char *shell_file) { double rmin, rmax; int i; FILE *fh; struct fom_shells *shells; struct fom_context *nmeas_ctx; struct fom_context *red_ctx; struct fom_context *snr_ctx; struct fom_context *mean_ctx; struct fom_context *compl_ctx; fh = fopen(shell_file, "w"); if ( fh == NULL ) { ERROR("Couldn't open '%s'\n", shell_file); return; } resolution_limits(list, cell, &rmin, &rmax); STATUS("1/d goes from %f to %f nm^-1\n", rmin/1e9, rmax/1e9); /* Fixed resolution shells if needed */ if ( rmin_fix > 0.0 ) rmin = rmin_fix; if ( rmax_fix > 0.0 ) rmax = rmax_fix; shells = fom_make_resolution_shells(rmin, rmax, nshells); STATUS("Overall values within specified resolution range:\n"); nmeas_ctx = fom_calculate(list, NULL, cell, shells, FOM_NUM_MEASUREMENTS, 0, sym); red_ctx = fom_calculate(list, NULL, cell, shells, FOM_REDUNDANCY, 0, sym); snr_ctx = fom_calculate(list, NULL, cell, shells, FOM_SNR, 0, sym); mean_ctx = fom_calculate(list, NULL, cell, shells, FOM_MEAN_INTENSITY, 0, sym); compl_ctx = fom_calculate(list, NULL, cell, shells, FOM_COMPLETENESS, 0, sym); STATUS("%.0f measurements in total.\n", fom_overall_value(nmeas_ctx)); STATUS("%li reflections in total.\n", fom_overall_num_reflections(compl_ctx)); STATUS("%li reflections possible.\n", fom_overall_num_possible(compl_ctx)); STATUS("Overall = %f\n", fom_overall_value(snr_ctx)); STATUS("Overall redundancy = %f measurements/unique reflection\n", fom_overall_value(red_ctx)); STATUS("Overall completeness = %f %%\n", 100.0*fom_overall_value(compl_ctx)); fprintf(fh, "Center 1/nm # refs Possible Compl " "Meas Red SNR Mean I d(A) " "Min 1/nm Max 1/nm\n"); for ( i=0; irmins[i]*1.0e-9, shells->rmaxs[i]*1.0e-9); } fclose(fh); STATUS("Resolution shell information written to %s.\n", shell_file); } static void check_highres() { static int have = 0; if ( have ) { ERROR("You cannot use --rmax and --highres at the same time.\n"); exit(1); } have = 1; } static void check_lowres() { static int have = 0; if ( have ) { ERROR("You cannot use --rmin and --lowres at the same time.\n"); exit(1); } have = 1; } int main(int argc, char *argv[]) { int c; UnitCell *cell; char *file = NULL; char *sym_str = NULL; char *sym_str_fromfile = NULL; SymOpList *sym; RefList *raw_list; RefList *list; char *cellfile = NULL; float rmin_fix = -1.0; float rmax_fix = -1.0; float sigma_cutoff = -INFINITY; int nshells = 10; int have_nshells = 0; char *shell_file = NULL; int wilson = 0; int ltest = 0; int ignorenegs = 0; int zeronegs = 0; float highres, lowres; struct fom_rejections rej; /* Long options */ const struct option longopts[] = { {"help", 0, NULL, 'h'}, {"version", 0, NULL, 9 }, {"symmetry", 1, NULL, 'y'}, {"pdb", 1, NULL, 'p'}, {"rmin", 1, NULL, 2}, {"rmax", 1, NULL, 3}, {"sigma-cutoff", 1, NULL, 4}, {"nshells", 1, NULL, 5}, {"shell-file", 1, NULL, 6}, {"highres", 1, NULL, 7}, {"lowres", 1, NULL, 8}, {"wilson", 0, &wilson, 1}, {"ltest", 0, <est, 1}, {"ignore-negs", 0, &ignorenegs, 1}, {"zero-negs", 0, &zeronegs, 1}, {0, 0, NULL, 0} }; /* Short options */ while ((c = getopt_long(argc, argv, "hy:p:", longopts, NULL)) != -1) { switch (c) { case 'h' : show_help(argv[0]); return 0; case 9 : printf("CrystFEL: %s\n", crystfel_version_string()); printf("%s\n", crystfel_licence_string()); return 0; case 'y' : sym_str = strdup(optarg); break; case 'p' : cellfile = strdup(optarg); break; case 0 : break; case 2 : check_lowres(); if ( sscanf(optarg, "%e", &rmin_fix) != 1 ) { ERROR("Invalid value for --rmin\n"); return 1; } break; case 3 : check_highres(); if ( sscanf(optarg, "%e", &rmax_fix) != 1 ) { ERROR("Invalid value for --rmax\n"); return 1; } break; case 4 : if ( sscanf(optarg, "%f", &sigma_cutoff) != 1 ) { ERROR("Invalid value for --sigma-cutoff\n"); return 1; } STATUS("WARNING: You are using --sigma-cutoff. " "Be aware that the figures of merit will not " "reflect the entire data set!\n"); break; case 5 : if ( sscanf(optarg, "%i", &nshells) != 1 ) { ERROR("Invalid value for --nshells\n"); return 1; } have_nshells = 1; break; case 6 : shell_file = strdup(optarg); break; case 7 : check_highres(); if ( sscanf(optarg, "%e", &highres) != 1 ) { ERROR("Invalid value for --highres\n"); return 1; } rmax_fix = 1.0 / (highres/1e10); break; case 8 : check_lowres(); if ( sscanf(optarg, "%e", &lowres) != 1 ) { ERROR("Invalid value for --lowres\n"); return 1; } rmin_fix = 1.0 / (lowres/1e10); break; case '?' : break; default : ERROR("Unhandled option '%c'\n", c); break; } } if ( argc != (optind+1) ) { ERROR("Please provide exactly one HKL file to check.\n"); return 1; } if ( !ltest && (ignorenegs || zeronegs) ) { ERROR("WARNING: You are using --zero-negs or --ignore-negs " "even though it's not required.\n"); ERROR("The figures of merit will not reflect the entire data " "set!\n"); } file = strdup(argv[optind++]); if ( cellfile == NULL ) { ERROR("You need to provide a unit cell.\n"); return 1; } cell = load_cell_from_file(cellfile); if ( cell == NULL ) { ERROR("Failed to load cell.\n"); return 1; } free(cellfile); raw_list = read_reflections_2(file, &sym_str_fromfile); if ( raw_list == NULL ) { ERROR("Couldn't read file '%s'\n", file); return 1; } free(file); if ( sym_str == NULL ) { if ( sym_str_fromfile != NULL ) { STATUS("Using symmetry from reflection file: %s\n", sym_str_fromfile); sym_str = sym_str_fromfile; } else { sym_str = strdup("1"); } } sym = get_pointgroup(sym_str); free(sym_str); if ( shell_file == NULL ) shell_file = strdup("shells.dat"); /* Check that the intensities have the correct symmetry */ if ( check_list_symmetry(raw_list, sym) ) { ERROR("The input reflection list does not appear to" " have symmetry %s\n", symmetry_name(sym)); if ( cell_get_lattice_type(cell) == L_MONOCLINIC ) { ERROR("You may need to specify the unique axis in your " "point group. The default is unique axis c.\n"); ERROR("See 'man crystfel' for more details.\n"); } return 1; } /* Reject some reflections */ rej = fom_select_reflections(raw_list, &list, cell, sym, rmin_fix, rmax_fix, sigma_cutoff, ignorenegs, zeronegs, 0); STATUS("Discarded %i reflections (out of %i) with I/sigma(I) < %f\n", rej.low_snr, num_reflections(raw_list), sigma_cutoff); reflist_free(raw_list); if ( rej.negative_deleted > 0 ) { STATUS("Discarded %i reflections because they had negative " "intensities.\n", rej.negative_deleted); } if ( rej.negative_zeroed > 0 ) { STATUS("Set %i negative intensities to zero\n", rej.negative_zeroed); } if ( rej.outside_resolution_range > 0 ) { STATUS("%i reflections rejected because they were outside the " "resolution range.\n", rej.outside_resolution_range); } if ( rej.nan_inf_value ) { STATUS("WARNING: %i reflections had infinite or invalid values" " of I or sigma(I).\n", rej.nan_inf_value); } if ( wilson ) { if ( !have_nshells ) nshells = 50; wilson_plot(list, cell, sym, rmin_fix, rmax_fix, nshells, shell_file); } else if ( ltest ) { if ( !have_nshells ) nshells = 50; if ( !ignorenegs && !zeronegs ) { ERROR("For the L-test you must specify either" "--ignore-negs or --zero-negs.\n"); return 1; } l_test(list, cell, sym, rmin_fix, rmax_fix, nshells, shell_file); } else { plot_shells(list, cell, sym, rmin_fix, rmax_fix, nshells, shell_file); } free_symoplist(sym); reflist_free(list); cell_free(cell); free(shell_file); return 0; }