Beispiel #1
0
  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))
Beispiel #3
0
  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
Beispiel #5
0
  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