def __call__(self, experiments, reflections, add_correction_column=False): result = flex.reflection_table() for expt_id, experiment in enumerate(experiments): refls = reflections.select(reflections['id'] == expt_id) beam = experiment.beam # Remove the need for pixel size within cxi.merge. Allows multipanel detector with dissimilar panels. # Relies on new frame extractor code called by dials.stills_process that writes s0, s1 and polarization normal # vectors all to the integration pickle. Future path (IE THIS CODE): use dials json and reflection file. s0_vec = matrix.col(beam.get_s0()).normalize() s0_polar_norm = beam.get_polarization_normal() s1_vec = refls['s1'] Ns1 = len(s1_vec) # project the s1_vector onto the plane normal to s0. Get result by subtracting the # projection of s1 onto s0, which is (s1.dot.s0_norm)s0_norm s0_norm = flex.vec3_double(Ns1,s0_vec) s1_proj = (s1_vec.dot(s0_norm))*s0_norm s1_in_normal_plane = s1_vec - s1_proj # Now want the polar angle between the projected s1 and the polarization normal s0_polar_norms = flex.vec3_double(Ns1,s0_polar_norm) dotprod = (s1_in_normal_plane.dot(s0_polar_norms)) costheta = dotprod/(s1_in_normal_plane.norms()) theta = flex.acos(costheta) cos_two_polar_angle = flex.cos(2.0*theta) # gives same as old answer to ~1% but not exact. Not sure why, should not matter. tt_vec = experiment.crystal.get_unit_cell().two_theta(miller_indices = refls['miller_index'], wavelength = beam.get_wavelength()) cos_tt_vec = flex.cos(tt_vec) sin_tt_vec = flex.sin(tt_vec) cos_sq_tt_vec = cos_tt_vec * cos_tt_vec sin_sq_tt_vec = sin_tt_vec * sin_tt_vec P_nought_vec = 0.5 * (1. + cos_sq_tt_vec) F_prime = -1.0 # Hard-coded value defines the incident polarization axis P_prime = 0.5 * F_prime * cos_two_polar_angle * sin_sq_tt_vec # added as a diagnostic #prange=P_nought_vec - P_prime #other_F_prime = 1.0 #otherP_prime = 0.5 * other_F_prime * cos_two_polar_angle * sin_sq_tt_vec #otherprange=P_nought_vec - otherP_prime #diff2 = flex.abs(prange - otherprange) #print >> out, "mean diff is",flex.mean(diff2), "range",flex.min(diff2), flex.max(diff2) # done correction = 1 / ( P_nought_vec - P_prime ) refls['intensity.sum.value'] = refls['intensity.sum.value'] * correction refls['intensity.sum.variance'] = refls['intensity.sum.variance'] * correction**2 # propagated error # This corrects observations for polarization assuming 100% polarization on # one axis (thus the F_prime = -1.0 rather than the perpendicular axis, 1.0) # Polarization model as described by Kahn, Fourme, Gadet, Janin, Dumas & Andre # (1982) J. Appl. Cryst. 15, 330-337, equations 13 - 15. if add_correction_column: refls['polarization_correction'] = correction result.extend(refls) return result
def compute_functional(vector): '''computes functional for 2-D grid search''' two_pi_S_dot_v = 2 * math.pi * self.reciprocal_lattice_points.dot(vector) return flex.sum(flex.cos(two_pi_S_dot_v))
def adjust_errors(self): """ Propagate errors to the scaled and merged intensity errors based on statistical error propagation. This uses 1) and estimate of the errors in the post-refined parametes from the observed population and 2) partial derivatives of the scaled intensity with respect to each of the post-refined parameters. """ assert self.scaler.params.postrefinement.algorithm == 'rs' refls = self.scaler.ISIGI ct = self.scaler.crystal_table # Note, since the rs algorithm doesn't explicitly refine eta and deff separately, but insteads refines RS, # assume rs only incorporates information from deff and set eta to zero. ct['deff'] = 1/ct['RS'] ct['eta'] = flex.double(len(ct), 0) # Compute errors by examining distributions of parameters stats_thetax = flex.mean_and_variance(ct['thetax']) stats_thetay = flex.mean_and_variance(ct['thetay']) stats_lambda = flex.mean_and_variance(ct['wavelength']) #stats_eta = flex.mean_and_variance(ct['ETA']) stats_deff = flex.mean_and_variance(ct['deff']) stats_rs = flex.mean_and_variance(ct['RS']) sigma_thetax = stats_thetax.unweighted_sample_standard_deviation() sigma_thetay = stats_thetay.unweighted_sample_standard_deviation() sigma_lambda = stats_lambda.unweighted_sample_standard_deviation() sigma_eta = 0 #stats_eta.unweighted_sample_standard_deviation() sigma_deff = stats_deff.unweighted_sample_standard_deviation() sigma_rs = stats_rs.unweighted_sample_standard_deviation() print >> self.log, "ThetaX %.4f +/- %.4f" %(r2d(stats_thetax.mean()), r2d(sigma_thetax)) print >> self.log, "Thetay %.4f +/- %.4f" %(r2d(stats_thetay.mean()), r2d(sigma_thetay)) print >> self.log, "Wavelength %.4f +/- %.4f"%( stats_lambda.mean(), sigma_lambda) #print "ETA %.4f +/- %.4f" %( stats_eta.mean(), sigma_eta) print >> self.log, "DEFF %.4f +/- %.4f" %( stats_deff.mean(), sigma_deff) print >> self.log, "RS %.6f +/- %.6f" %( stats_rs.mean(), sigma_rs) # notation: dP1_dP2 is derivative of parameter 1 with respect to parameter 2. Here, # for example, is the derivative of rx wrt thetax drx_dthetax = flex.mat3_double() dry_dthetay = flex.mat3_double() s0hat = flex.vec3_double(len(refls), (0,0,-1)) ex = col((1,0,0)) ey = col((0,1,0)) # Compute derivatives sre = symmetrize_reduce_enlarge(self.scaler.params.target_space_group.group()) c_gstar_params = None gstar_params = None gstar_derivatives = None for i in xrange(len(ct)): n_refl = ct['n_refl'][i] # Derivatives of rx/y wrt thetax/y come from cctbx drx_dthetax.extend(flex.mat3_double(n_refl, ex.axis_and_angle_as_r3_derivative_wrt_angle(ct['thetax'][i]))) dry_dthetay.extend(flex.mat3_double(n_refl, ey.axis_and_angle_as_r3_derivative_wrt_angle(ct['thetay'][i]))) # Derivatives of the B matrix wrt to the unit cell parameters also come from cctbx sre.set_orientation(orientation=ct['b_matrix'][i]) p = sre.forward_independent_parameters() dB_dp = sre.forward_gradients() if gstar_params is None: assert gstar_derivatives is None and c_gstar_params is None c_gstar_params = [flex.double() for j in xrange(len(p))] gstar_params = [flex.double() for j in xrange(len(p))] gstar_derivatives = [flex.mat3_double() for j in xrange(len(p))] assert len(p) == len(dB_dp) == len(gstar_params) == len(gstar_derivatives) == len(c_gstar_params) for j in xrange(len(p)): c_gstar_params[j].append(p[j]) gstar_params[j].extend(flex.double(n_refl, p[j])) gstar_derivatives[j].extend(flex.mat3_double(n_refl, tuple(dB_dp[j]))) # Compute the error in the unit cell terms from the distribution of unit cell parameters provided print >> self.log, "Free G* parameters" sigma_gstar = [] for j in xrange(len(gstar_params)): stats = flex.mean_and_variance(c_gstar_params[j]) print >> self.log, "G* %d %.4f *1e-5 +/- %.4f *1e-5"%(j, stats.mean()*1e5, stats.unweighted_sample_standard_deviation()*1e5) sigma_gstar.append(stats.unweighted_sample_standard_deviation()) # Compute the scalar terms used while computing derivatives r = self.compute_intensity_parameters() # Begin computing derivatives sigma_Iobs = refls['scaled_intensity']/refls['isigi'] dI_dIobs = 1/r['D'] def compute_dI_dp(dq_dp): """ Deriviatives of the scaled intensity I wrt to thetax, thetay and the unit cell parameters are computed the same, starting with the deriviatives of those parameters wrt to q """ dqlen_dp = r['q'].dot(dq_dp)/r['qlen'] dd_dp = -(1/(r['qlen']**2)) * dqlen_dp drs_dp = -(r['eta']/(2 * r['d']**2)) * dd_dp dslen_dp = r['s'].dot(dq_dp)/r['slen'] drhsq_dp = 2 * (r['slen'] - (1/r['wavelength'])) * dslen_dp dPn_dp = 2 * r['rs'] * drs_dp dPd_dp = 2 * ((r['rs'] * drs_dp) + drhsq_dp) dP_dp = ((r['p_d'] * dPn_dp)-(r['p_n'] * dPd_dp))/(r['p_d']**2) dI_dp = -(refls['iobs']/(r['partiality']**2 * r['G'] * r['eepsilon'])) * dP_dp return dI_dp # Derivatives wrt the unit cell parameters dI_dgstar = [] for j in xrange(len(gstar_params)): dI_dgstar.append(compute_dI_dp(r['ry'] * r['rx'] * r['u'] * gstar_derivatives[j] * r['h'])) # Derivatives wrt the crystal orientation dI_dthetax = compute_dI_dp(r['ry'] * drx_dthetax * r['u'] * r['b'] * r['h']) dI_dthetay = compute_dI_dp(dry_dthetay * r['rx'] * r['u'] * r['b'] * r['h']) # Derivatives wrt to the wavelength dthetah_dlambda = 1/(flex.sqrt(1 - ((r['wavelength']/(2 * r['d']))**2)) * 2 * r['d']) den_dlambda = flex.cos(r['thetah']) * dthetah_dlambda der_dlambda = ((r['wavelength'] * den_dlambda) - r['sinthetah'])/r['wavelength']**2 depsilon_dlambda = -16 * r['B'] * r['er'] * der_dlambda ds0_dlambda = s0hat*(-1/r['wavelength']**2) dslen_dlambda = r['s'].dot(ds0_dlambda)/r['slen'] drhsq_dlambda = 2*(r['slen']-(1/r['wavelength']))*(dslen_dlambda+(1/r['wavelength']**2)) dP_dlambda = -2*(r['p_n']/r['p_d']**2) * drhsq_dlambda dD_dlambda = (r['G'] * r['eepsilon'] * dP_dlambda) + (r['partiality'] * r['G'] * r['eepsilon'] * depsilon_dlambda) dI_dlambda = -(refls['iobs']/r['D']**2) * dD_dlambda # Derivatives wrt to the deff drs_deff = -1/(r['deff']**2) dPn_deff = 2 * r['rs'] * drs_deff dPd_deff = 2 * r['rs'] * drs_deff dP_deff = ((r['p_d'] * dPn_deff)-(r['p_n'] * dPd_deff))/(r['p_d']**2) dI_deff = -(refls['iobs']/(r['partiality']**2 * r['G'] * r['eepsilon'])) * dP_deff # Derivatives wrt to eta drs_deta = 1/(2*r['d']) dPn_deta = 2 * r['rs'] * drs_deta dPd_deta = 2 * r['rs'] * drs_deta dP_deta = ((r['p_d']*dPn_deta)-(r['p_n']*dPd_deta))/(r['p_d']**2) dI_deta = -(refls['iobs']/(r['partiality']**2 * r['G'] * r['eepsilon'])) * dP_deta if True: # Show comparisons to finite differences n_cryst_params = sre.constraints.n_independent_params() print "Showing finite differences and derivatives for each parameter (first few reflections only)" for parameter_name, table, derivatives, delta, in zip(['iobs', 'thetax', 'thetay', 'wavelength', 'deff', 'eta'] + ['c%d'%cp for cp in xrange(n_cryst_params)], [refls, ct, ct, ct, ct, ct] + [ct]*n_cryst_params, [dI_dIobs, dI_dthetax, dI_dthetay, dI_dlambda, dI_deff, dI_deta] + dI_dgstar, [1e-7]*6 + [1e-11]*n_cryst_params): finite_g = self.finite_difference(parameter_name, table, delta) print parameter_name for refl_id in xrange(min(10, len(refls))): print "%d % 21.1f % 21.1f"%(refl_id, finite_g[refl_id], derivatives[refl_id]) stats = flex.mean_and_variance(finite_g-derivatives) stats_finite = flex.mean_and_variance(finite_g) percent = 0 if stats_finite.mean() == 0 else 100*stats.mean()/stats_finite.mean() print "Mean difference between finite and analytical: % 24.4f +/- % 24.4f (%8.3f%% of finite d.)"%( \ stats.mean(), stats.unweighted_sample_standard_deviation(), percent) print # Propagate errors refls['isigi'] = refls['scaled_intensity'] / flex.sqrt(((sigma_Iobs**2 * dI_dIobs**2) + sum([sigma_gstar[j]**2 * dI_dgstar[j]**2 for j in xrange(len(sigma_gstar))]) + (sigma_thetax**2 * dI_dthetax**2) + (sigma_thetay**2 * dI_dthetay**2) + (sigma_lambda**2 * dI_dlambda**2) + (sigma_deff**2 * dI_deff**2) + (sigma_eta**2 * dI_deta**2))) # Show results of propagation from scitbx.math import five_number_summary all_data = [(refls['iobs'], "Iobs"), (sigma_Iobs, "Original errors"), (1/r['D'], "Total scale factor"), (refls['iobs']/r['D'], "Inflated intensities"), (refls['scaled_intensity']/refls['isigi'], "Propagated errors"), (flex.sqrt(sigma_Iobs**2 * dI_dIobs**2), "Iobs term"), (flex.sqrt(sigma_thetax**2 * dI_dthetax**2), "Thetax term"), (flex.sqrt(sigma_thetay**2 * dI_dthetay**2), "Thetay term"), (flex.sqrt(sigma_lambda**2 * dI_dlambda**2), "Wavelength term"), (flex.sqrt(sigma_deff**2 * dI_deff**2), "Deff term"), (flex.sqrt(sigma_eta**2 * dI_deta**2), "Eta term")] + \ [(flex.sqrt(sigma_gstar[j]**2 * dI_dgstar[j]**2), "Gstar term %d"%j) for j in xrange(len(sigma_gstar))] print >> self.log, "%20s % 20s % 20s % 20s"%("Data name","Quartile 1", "Median", "Quartile 3") for data, title in all_data: fns = five_number_summary(data) print >> self.log, "%20s % 20d % 20d % 20d"%(title, fns[1], fns[2], fns[3]) # Final terms for cxi.merge self.scaler.summed_weight= flex.double(self.scaler.n_refl, 0.) self.scaler.summed_wt_I = flex.double(self.scaler.n_refl, 0.) Intensity = refls['scaled_intensity'] sigma = Intensity / refls['isigi'] variance = sigma * sigma for i in xrange(len(refls)): j = refls['miller_id'][i] self.scaler.summed_wt_I[j] += Intensity[i] / variance[i] self.scaler.summed_weight[j] += 1 / variance[i]
def run(self, experiments, reflections): self.logger.log_step_time("POLARIZATION_CORRECTION") result = flex.reflection_table() for experiment in experiments: refls = reflections.select( reflections['exp_id'] == experiment.identifier) if len(refls) == 0: continue beam = experiment.beam # Remove the need for pixel size within cxi.merge. Allows multipanel detector with dissimilar panels. # Relies on new frame extractor code called by dials.stills_process that writes s0, s1 and polarization normal # vectors all to the integration pickle. Future path (IE THIS CODE): use dials json and reflection file. s0_vec = matrix.col(beam.get_s0()).normalize() s0_polar_norm = beam.get_polarization_normal() s1_vec = refls['s1'] Ns1 = len(s1_vec) # project the s1_vector onto the plane normal to s0. Get result by subtracting the # projection of s1 onto s0, which is (s1.dot.s0_norm)s0_norm s0_norm = flex.vec3_double(Ns1, s0_vec) s1_proj = (s1_vec.dot(s0_norm)) * s0_norm s1_in_normal_plane = s1_vec - s1_proj # Now want the polar angle between the projected s1 and the polarization normal s0_polar_norms = flex.vec3_double(Ns1, s0_polar_norm) dotprod = (s1_in_normal_plane.dot(s0_polar_norms)) costheta = dotprod / (s1_in_normal_plane.norms()) theta = flex.acos(costheta) cos_two_polar_angle = flex.cos(2.0 * theta) # gives same as old answer to ~1% but not exact. Not sure why, should not matter. tt_vec = experiment.crystal.get_unit_cell().two_theta( miller_indices=refls['miller_index'], wavelength=beam.get_wavelength()) cos_tt_vec = flex.cos(tt_vec) sin_tt_vec = flex.sin(tt_vec) cos_sq_tt_vec = cos_tt_vec * cos_tt_vec sin_sq_tt_vec = sin_tt_vec * sin_tt_vec P_nought_vec = 0.5 * (1. + cos_sq_tt_vec) F_prime = -1.0 # Hard-coded value defines the incident polarization axis P_prime = 0.5 * F_prime * cos_two_polar_angle * sin_sq_tt_vec # added as a diagnostic #prange=P_nought_vec - P_prime #other_F_prime = 1.0 #otherP_prime = 0.5 * other_F_prime * cos_two_polar_angle * sin_sq_tt_vec #otherprange=P_nought_vec - otherP_prime #diff2 = flex.abs(prange - otherprange) #print >> out, "mean diff is",flex.mean(diff2), "range",flex.min(diff2), flex.max(diff2) # done correction = 1 / (P_nought_vec - P_prime) refls['intensity.sum.value'] = refls[ 'intensity.sum.value'] * correction refls['intensity.sum.variance'] = refls[ 'intensity.sum.variance'] * correction**2 # propagated error # This corrects observations for polarization assuming 100% polarization on # one axis (thus the F_prime = -1.0 rather than the perpendicular axis, 1.0) # Polarization model as described by Kahn, Fourme, Gadet, Janin, Dumas & Andre # (1982) J. Appl. Cryst. 15, 330-337, equations 13 - 15. result.extend(refls) if len(reflections) > 0: self.logger.log( "Applied polarization correction. Mean intensity changed from %.2f to %.2f" % (flex.mean(reflections['intensity.sum.value']), flex.mean(result['intensity.sum.value']))) self.logger.log_step_time("POLARIZATION_CORRECTION", True) self.logger.log("Memory usage: %d MB" % get_memory_usage()) # Remove 's1' column from the reflection table from xfel.merging.application.reflection_table_utils import reflection_table_utils reflections = reflection_table_utils.prune_reflection_table_keys( reflections=result, keys_to_delete=['s1']) self.logger.log("Pruned reflection table") self.logger.log("Memory usage: %d MB" % get_memory_usage()) return experiments, reflections
def dI_derrorterms(self): refls = self.scaler.ISIGI ct = self.scaler.crystal_table # notation: dP1_dP2 is derivative of parameter 1 with respect to parameter 2. Here, # for example, is the derivative of rx wrt thetax drx_dthetax = flex.mat3_double() dry_dthetay = flex.mat3_double() s0hat = flex.vec3_double(len(refls), (0,0,-1)) ex = col((1,0,0)) ey = col((0,1,0)) # Compute derivatives sre = symmetrize_reduce_enlarge(self.scaler.params.target_space_group.group()) gstar_params = None gstar_derivatives = None for i in xrange(len(ct)): n_refl = ct['n_refl'][i] # Derivatives of rx/y wrt thetax/y come from cctbx drx_dthetax.extend(flex.mat3_double(n_refl, ex.axis_and_angle_as_r3_derivative_wrt_angle(ct['thetax'][i]))) dry_dthetay.extend(flex.mat3_double(n_refl, ey.axis_and_angle_as_r3_derivative_wrt_angle(ct['thetay'][i]))) # Derivatives of the B matrix wrt to the unit cell parameters also come from cctbx sre.set_orientation(orientation=ct['b_matrix'][i]) p = sre.forward_independent_parameters() dB_dp = sre.forward_gradients() if gstar_params is None: assert gstar_derivatives is None gstar_params = [flex.double() for j in xrange(len(p))] gstar_derivatives = [flex.mat3_double() for j in xrange(len(p))] assert len(p) == len(dB_dp) == len(gstar_params) == len(gstar_derivatives) for j in xrange(len(p)): gstar_params[j].extend(flex.double(n_refl, p[j])) gstar_derivatives[j].extend(flex.mat3_double(n_refl, tuple(dB_dp[j]))) # Compute the scalar terms used while computing derivatives self.r = r = self.compute_intensity_parameters() # Begin computing derivatives sigma_Iobs = refls['scaled_intensity']/refls['isigi'] dI_dIobs = 1/r['D'] def compute_dI_dp(dq_dp): """ Deriviatives of the scaled intensity I wrt to thetax, thetay and the unit cell parameters are computed the same, starting with the deriviatives of those parameters wrt to q """ dqlen_dp = r['q'].dot(dq_dp)/r['qlen'] dd_dp = -(1/(r['qlen']**2)) * dqlen_dp drs_dp = -(r['eta']/(2 * r['d']**2)) * dd_dp dslen_dp = r['s'].dot(dq_dp)/r['slen'] drhsq_dp = 2 * (r['slen'] - (1/r['wavelength'])) * dslen_dp dPn_dp = 2 * r['rs'] * drs_dp dPd_dp = 2 * ((r['rs'] * drs_dp) + drhsq_dp) dP_dp = ((r['p_d'] * dPn_dp)-(r['p_n'] * dPd_dp))/(r['p_d']**2) dI_dp = -(refls['iobs']/(r['partiality']**2 * r['G'] * r['eepsilon'])) * dP_dp return dI_dp # Derivatives wrt the unit cell parameters dI_dgstar = [] for j in xrange(len(gstar_params)): dI_dgstar.append(compute_dI_dp(r['ry'] * r['rx'] * r['u'] * gstar_derivatives[j] * r['h'])) # Derivatives wrt the crystal orientation dI_dthetax = compute_dI_dp(r['ry'] * drx_dthetax * r['u'] * r['b'] * r['h']) dI_dthetay = compute_dI_dp(dry_dthetay * r['rx'] * r['u'] * r['b'] * r['h']) # Derivatives wrt to the wavelength dthetah_dlambda = 1/(flex.sqrt(1 - ((r['wavelength']/(2 * r['d']))**2)) * 2 * r['d']) den_dlambda = flex.cos(r['thetah']) * dthetah_dlambda der_dlambda = ((r['wavelength'] * den_dlambda) - r['sinthetah'])/r['wavelength']**2 depsilon_dlambda = -16 * r['B'] * r['er'] * der_dlambda ds0_dlambda = s0hat*(-1/r['wavelength']**2) dslen_dlambda = r['s'].dot(ds0_dlambda)/r['slen'] drhsq_dlambda = 2*(r['slen']-(1/r['wavelength']))*(dslen_dlambda+(1/r['wavelength']**2)) dP_dlambda = -2*(r['p_n']/r['p_d']**2) * drhsq_dlambda dD_dlambda = (r['G'] * r['eepsilon'] * dP_dlambda) + (r['partiality'] * r['G'] * r['eepsilon'] * depsilon_dlambda) dI_dlambda = -(refls['iobs']/r['D']**2) * dD_dlambda # Derivatives wrt to the deff drs_deff = -1/(r['deff']**2) dPn_deff = 2 * r['rs'] * drs_deff dPd_deff = 2 * r['rs'] * drs_deff dP_deff = ((r['p_d'] * dPn_deff)-(r['p_n'] * dPd_deff))/(r['p_d']**2) dI_deff = -(refls['iobs']/(r['partiality']**2 * r['G'] * r['eepsilon'])) * dP_deff # Derivatives wrt to eta (unused for RS refinement) # drs_deta = 1/(2*r['d']) # dPn_deta = 2 * r['rs'] * drs_deta # dPd_deta = 2 * r['rs'] * drs_deta # dP_deta = ((r['p_d']*dPn_deta)-(r['p_n']*dPd_deta))/(r['p_d']**2) # dI_deta = -(refls['iobs']/(r['partiality']**2 * r['G'] * r['eepsilon'])) * dP_deta if self.verbose: # Show comparisons to finite differences n_cryst_params = sre.constraints.n_independent_params() print "Showing finite differences and derivatives for each parameter (first few reflections only)" for parameter_name, table, derivatives, delta, in zip(['iobs', 'thetax', 'thetay', 'wavelength', 'deff'] + ['c%d'%cp for cp in xrange(n_cryst_params)], [refls, ct, ct, ct, ct] + [ct]*n_cryst_params, [dI_dIobs, dI_dthetax, dI_dthetay, dI_dlambda, dI_deff] + dI_dgstar, [1e-7]*5 + [1e-11]*n_cryst_params): finite_g = self.finite_difference(parameter_name, table, delta) print parameter_name for refl_id in xrange(min(10, len(refls))): print "%d % 21.1f % 21.1f"%(refl_id, finite_g[refl_id], derivatives[refl_id]) stats = flex.mean_and_variance(finite_g-derivatives) stats_finite = flex.mean_and_variance(finite_g) percent = 0 if stats_finite.mean() == 0 else 100*stats.mean()/stats_finite.mean() print "Mean difference between finite and analytical: % 24.4f +/- % 24.4f (%8.3f%% of finite d.)"%( \ stats.mean(), stats.unweighted_sample_standard_deviation(), percent) print return [dI_dIobs, dI_dthetax, dI_dthetay, dI_dlambda, dI_deff] + dI_dgstar
def wavelengths_from_gaussians(experiments, reflections, mosaic_parameters): """ Given a set of mosaic parameters, use gaussian bandpass and mosaicity to estimate a wavelength for each reflection. Details: For a given reflection, the reciprocal lattice point vector q = Ah, where A is the reciprocal A matrix and h is the reflection's miller index. Construct a vector e1 orthagonal to s0 and q. @param experiments ExperimentList. If crystal.band_pass is set, use it. Otherwise, estimate the band pass using estimate_bandpass @param reflections flex.reflection_table Needs to contain the column reflection_wavelength_from_pixels @param mosaic_parameters Tuple of domain size (angstroms) and half mosaic angle (degrees). If None, use the mosaic parameters from each crystal model. """ if "reflection_wavelength_from_pixels" not in reflections: return reflections if mosaic_parameters is not None: domain_size_ang, half_mosaicity_deg = mosaic_parameters print( "Computing per-reflection wavelengths from domain size", domain_size_ang, "(ang), half mosaic angle", half_mosaicity_deg, "(deg), and bandpass derived from each image", ) table = flex.reflection_table() new_wavelengths = flex.double() def gaussian_product(mean1, sigma1, mean2, sigma2): """Jiffy function to multiply two gaussians. Formula from P. Bromiley, "Products and convolutions of Gaussian distributions," Medical School, Univ. Manchester, Manchester, UK, Tech. Rep, vol. 3, p. 2003, 2003. """ ssq1 = sigma1**2 ssq2 = sigma2**2 mean = ((mean1 * ssq2) + (mean2 * ssq1)) / (ssq1 + ssq2) sigma = flex.sqrt((ssq1 * ssq2) / (ssq1 + ssq2)) return mean, sigma for expt_id, expt in enumerate(experiments): refls = reflections.select(reflections["id"] == expt_id) table.extend(refls) if mosaic_parameters is None: domain_size_ang = expt.crystal.get_domain_size_ang() half_mosaicity_deg = expt.crystal.get_half_mosaicity_deg() print( "Computing per-reflection wavelengths from domain size", domain_size_ang, "(ang), half mosaic angle", half_mosaicity_deg, "(deg), and bandpass derived from each image", ) # Determine how to obtain the bandpass if hasattr(expt.crystal, "bandpass"): wavelength_min, wavelength_max = expt.crystal.bandpass else: wavelength_min, wavelength_max = estimate_bandpass(refls) expt.crystal.bandpass = wavelength_min, wavelength_max unit_s0 = flex.vec3_double(len(refls), expt.beam.get_s0()).each_normalize() wavelength = expt.beam.get_wavelength() q = (flex.mat3_double(len(refls), expt.crystal.get_A()) * refls["miller_index"].as_vec3_double()) e1 = q.cross(unit_s0).each_normalize() # length of an arc l = 2pir * angle/2pi = r*angle. So angle = l/r combined_mosaic_angle_approximation = ( (2 / domain_size_ang) / q.norms()) + (half_mosaicity_deg * math.pi / 180) # Compute z angles # Angle between s0 and q z_mosaicity = math.pi - q.angle(unit_s0) # Angle between s0 and q rotated to be on the center of bandpass z_wavelength = flex.acos(wavelength * q.norms() / 2) # Angles between s0 and q vectors rotated to the extreme ends of the bandpass z_wavelength_min = flex.acos(wavelength_min * q.norms() / 2) z_wavelength_max = flex.acos(wavelength_max * q.norms() / 2) # Now assume two gaussians on a polar notation (IE x is angle between a q vector and s0). One gaussian for the # bandpass (assuming the width of the bandpass is one sigma) and one for the mosaicity (assuming the width of # the mosaic spread is one sigma). The product of the two gaussians is the illuminated volume, and the center # of that gaussian angle a q vector would take with s0 such that it maximizes the illuminated volume. mean_z, sigma_z = gaussian_product( z_wavelength, z_wavelength - z_wavelength_max, z_mosaicity, combined_mosaic_angle_approximation, ) # Compute the wavelengths given the illuminated volume angle mean_z lmbda = flex.cos(mean_z) / (q.norms() / 2) new_wavelengths.extend(lmbda) assert (new_wavelengths <= 0).count(True) == 0 reflections[ "reflection_wavelength_from_gaussian_mosaicity_and_bandpass"] = new_wavelengths return reflections
def tophat_vector_wavelengths(experiments, reflections, mosaic_parameters): """ Given a set of mosaic parameters, use vectors to estimate a wavelength for each reflection Details: For a given reflection, the reciprocal lattice point vector q = Ah, where A is the reciprocal A matrix and h is the reflection's miller index. Construct a vector e1 orthagonal to s0 and q. Construct 4 more vectors of length equal to the magnitude of q, and that lie in the plane that is normal to e1: q_mos_inner and q_mos_outer: q vectors rotated into or out of the Ewald sphere by an angle equal to the half mosaic angle + the angle inscribed by adding an arc length equal to 2/domain size (angstroms). Call this angle the combined mosaic angle approximation. q_wavelength_min and q_wavelength_max: q vectors rotated on an ewald sphere with radius 1/bandpass minimum or 1/bandpass maximum. Consider now the pairs of vectors wavelength min/max and q_mos inner/outer. If neither q_mos vectors lies between the two wavelength vectors, then assign a refletion's wavelength to either wavelength min or max, depending on which is closest. Otherwise, find two vectors that lie between wavelength min/max. For example if q_mos inner lies between them, but outer does not, the two vectors will be q_mos inner and q wavelength min. If both q_mos inner and outer lie between wavelength min/max, then the two vetors will be q_mos inner and outer. Once the two vectors have been identified, define a new q vector which is the average of the two vectors. Determine the wavelength that would allow this q vector to be in the diffracting condition. Report that wavelength in the column reflection_wavelength_from_mosaicity_and_bandpass. Because these determinations involve hard cutoffs instead of gaussians, the wavelengths are determined in a manner similar to the overlap of tophat functions. @param experiments ExperimentList. If crystal.band_pass is set, use it. Otherwise, estimate the band pass using estimate_bandpass @param reflections flex.reflection_table Needs to contain the column reflection_wavelength_from_pixels @param mosaic_parameters Tuple of domain size (angstroms) and half mosaic angle (degrees) """ if "reflection_wavelength_from_pixels" not in reflections: return reflections domain_size_ang, half_mosaicity_deg = mosaic_parameters print( "Computing per-reflection wavelengths from domain size", domain_size_ang, "(ang), half mosaic angle", half_mosaicity_deg, "(deg), and bandpass derived from each image", ) table = flex.reflection_table() new_wavelengths = flex.double() # Keep track of the various cases case_0 = case_1 = case_2 = case_3 = case_4 = case_5 = 0 for expt_id, expt in enumerate(experiments): refls = reflections.select(reflections["id"] == expt_id) table.extend(refls) # Determine how to obtain the bandpass if hasattr(expt.crystal, "bandpass"): wavelength_min, wavelength_max = expt.crystal.bandpass else: wavelength_min, wavelength_max = estimate_bandpass(refls) expt.crystal.bandpass = wavelength_min, wavelength_max unit_s0 = flex.vec3_double(len(refls), expt.beam.get_s0()).each_normalize() wavelength = expt.beam.get_wavelength() q = (flex.mat3_double(len(refls), expt.crystal.get_A()) * refls["miller_index"].as_vec3_double()) e1 = q.cross(unit_s0).each_normalize() # length of an arc l = 2pir * angle/2pi = r*angle. So angle = l/r combined_mosaic_angle_approximation = ( (2 / domain_size_ang) / q.norms()) + (half_mosaicity_deg * math.pi / 180) q_mos_inner = q.rotate_around_origin( e1, -combined_mosaic_angle_approximation) q_mos_outer = q.rotate_around_origin( e1, combined_mosaic_angle_approximation) # Z: angle between q and s0 z_mos_inner = math.pi - q_mos_inner.angle(unit_s0) z_mos_outer = math.pi - q_mos_outer.angle(unit_s0) lmbda_bigger = flex.cos(z_mos_inner) / (q.norms() / 2) lmbda_smaller = flex.cos(z_mos_outer) / (q.norms() / 2) z_wavelength_min = flex.acos(wavelength_min * q.norms() / 2) z_wavelength_max = flex.acos(wavelength_max * q.norms() / 2) q_wavelength_min = ( unit_s0.rotate_around_origin(e1, math.pi + z_wavelength_min) * q.norms()) q_wavelength_max = ( unit_s0.rotate_around_origin(e1, math.pi + z_wavelength_max) * q.norms()) assert (lmbda_smaller < lmbda_bigger).count(False) == 0 sel = flex.bool(len(refls), True) image_wavelengths = flex.double(len(refls), 0) q_inner = flex.vec3_double() q_outer = flex.vec3_double() # Iterate through the reflections and sort each into one of the 6 cases for i in xrange(len(refls)): # Cases 0 and 1: both q_mos inner and outer are outside of the wavelength min/max vectors if lmbda_smaller[i] > wavelength_max: image_wavelengths[i] = wavelength_max sel[i] = False case_0 += 1 continue elif lmbda_bigger[i] < wavelength_min: image_wavelengths[i] = wavelength_min sel[i] = False case_1 += 1 continue sel[i] = True # Case 2: q_mos outer is between wavelengths min and max so use q_mos outer # Case 3: q_mos outer is outside of wavelength min so use wavelength min if lmbda_smaller[i] >= wavelength_min: q_outer.append(q_mos_outer[i]) case_2 += 1 else: q_outer.append(q_wavelength_min[i]) case_3 += 1 # Case 4: q_mos inner is between wavelengths min and max so use q_mos inner # Case 5: q_mos inner is outside of wavelength max so use wavelength max if lmbda_bigger[i] <= wavelength_max: q_inner.append(q_mos_inner[i]) case_4 += 1 else: q_inner.append(q_wavelength_max[i]) case_5 += 1 # Compute new reflection wavelengths new_q = (q_inner + q_outer) * 0.5 z = math.pi - new_q.angle(unit_s0.select(sel)) lmbda = flex.cos(z) / (new_q.norms() / 2) image_wavelengths.set_selected(sel, lmbda) new_wavelengths.extend(image_wavelengths) assert (new_wavelengths <= 0).count(True) == 0 reflections[ "reflection_wavelength_from_mosaicity_and_bandpass"] = new_wavelengths print("CASES", case_0, case_1, case_2, case_3, case_4, case_5) return reflections