def tst_nss_spherical_harmonics(): N=50 M=20 lfg = math.log_factorial_generator(N) nsssphe = math.nss_spherical_harmonics(M+5,50000,lfg) a = nsssphe.legendre_lm(8,2) b = nsssphe.legendre_lm_pc(8,2) for aa,bb in zip(a,b): assert abs(aa-bb)<1e-7 a = nsssphe.legendre_lm(14,3) b = nsssphe.legendre_lm_pc(14,3) for aa,bb in zip(a,b): assert abs(aa-bb)<1e-7 lm=[ (0,0), (3,3), (4,1) ] theta = flex.double(range(100))*3.14/100.0 phi = flex.double(range(100))*6.28/100.0 for tt in theta: for pp in phi: r = nsssphe.spherical_harmonic(20,10,tt,pp) rr = nsssphe.spherical_harmonic_pc(20,10,tt,pp) #print tt,pp, r.real, r.imag, 100.0*abs(r.real-rr.real)/(max(abs(r.real),1e-12)) , 100.0*abs(rr.imag-r.imag)/(max(abs(r.imag),1e-12)) assert 100.0*abs(r.real-rr.real)/(max(abs(r.real),1e-12))<2.0 assert 100.0*abs(rr.imag-r.imag)/(max(abs(r.imag),1e-12))<2.0
def plot_absorption_surface(physical_model): """Plot an absorption surface for a physical scaling model.""" d = { "absorption_surface": { "data": [], "layout": { "title": "Absorption correction surface", "xaxis": {"domain": [0, 1], "anchor": "y", "title": "theta (degrees)"}, "yaxis": {"domain": [0, 1], "anchor": "x", "title": "phi (degrees)"}, }, "help": absorption_help_msg, } } params = physical_model.components["absorption"].parameters order = int(-1.0 + ((1.0 + len(params)) ** 0.5)) lfg = scitbxmath.log_factorial_generator(2 * order + 1) STEPS = 50 phi = np.linspace(0, 2 * np.pi, 2 * STEPS) theta = np.linspace(0, np.pi, STEPS) THETA, _ = np.meshgrid(theta, phi) lmax = int(-1.0 + ((1.0 + len(params)) ** 0.5)) Intensity = np.ones(THETA.shape) counter = 0 sqrt2 = pymath.sqrt(2) nsssphe = scitbxmath.nss_spherical_harmonics(order, 50000, lfg) for l in range(1, lmax + 1): for m in range(-l, l + 1): for it, t in enumerate(theta): for ip, p in enumerate(phi): Ylm = nsssphe.spherical_harmonic(l, abs(m), t, p) if m < 0: r = sqrt2 * ((-1) ** m) * Ylm.imag elif m == 0: assert Ylm.imag == 0.0 r = Ylm.real else: r = sqrt2 * ((-1) ** m) * Ylm.real Intensity[ip, it] += params[counter] * r counter += 1 d["absorption_surface"]["data"].append( { "x": list(theta * 180.0 / np.pi), "y": list(phi * 180.0 / np.pi), "z": list(Intensity.T.tolist()), "type": "heatmap", "colorscale": "Viridis", "colorbar": {"title": "inverse <br>scale factor"}, "name": "absorption surface", "xaxis": "x", "yaxis": "y", } ) return d
def sph_harm_surf(l, m): from scitbx import math from scitbx.array_family import flex import math as pymath lfg = math.log_factorial_generator(2 * l + 1) nsssphe = math.nss_spherical_harmonics(l, 50000, lfg) theta = numpy.linspace(0, 2 * numpy.pi, 2 * STEPS) phi = numpy.linspace(0, numpy.pi, STEPS) THETA, PHI = numpy.meshgrid(theta, phi) R = numpy.cos(PHI**2) C = numpy.empty(THETA.shape, dtype=str) sqrt2 = pymath.sqrt(2) for it, t in enumerate(theta): for ip, p in enumerate(phi): Ylm = nsssphe.spherical_harmonic(l, abs(m), p, t) if m < 0: r = sqrt2 * ((-1)**m) * Ylm.imag elif m == 0: assert Ylm.imag == 0.0 r = Ylm.real else: r = sqrt2 * ((-1)**m) * Ylm.real R[ip, it] = pymath.fabs(r) if r < 0: C[ip, it] = "y" else: C[ip, it] = "b" X = R * numpy.sin(PHI) * numpy.cos(THETA) Y = R * numpy.sin(PHI) * numpy.sin(THETA) Z = R * numpy.cos(PHI) fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection="3d") plot = ax.plot_surface( X, Y, Z, rstride=1, cstride=1, facecolors=C, linewidth=0, antialiased=True, alpha=0.5, ) print("Saving %s..." % ("ylm%d%d.png" % (l, m))) plt.savefig("ylm%d%d.png" % (l, m))
def work(): from scitbx import math from scitbx.array_family import flex N=15 lfg = math.log_factorial_generator(N) nsssphe = math.nss_spherical_harmonics(6,50000,lfg) l = 2 m = 1 t = 1 p = 1 print nsssphe.spherical_harmonic(2, 1, 1, 1)
def work(): from scitbx import math from scitbx.array_family import flex N = 15 lfg = math.log_factorial_generator(N) nsssphe = math.nss_spherical_harmonics(6, 50000, lfg) l = 2 m = 1 t = 1 p = 1 print nsssphe.spherical_harmonic(2, 1, 1, 1)
def sph_harm_surf(l, m): from scitbx import math from scitbx.array_family import flex import math as pymath lfg = math.log_factorial_generator(2 * l + 1) nsssphe = math.nss_spherical_harmonics(l, 50000, lfg) theta = numpy.linspace(0, 2 * numpy.pi, 2*STEPS) phi = numpy.linspace(0, numpy.pi, STEPS) THETA, PHI = numpy.meshgrid(theta, phi) R = numpy.cos(PHI**2) C = numpy.empty(THETA.shape, dtype=str) sqrt2 = pymath.sqrt(2) for it, t in enumerate(theta): for ip, p in enumerate(phi): Ylm = nsssphe.spherical_harmonic(l, abs(m), p, t) if m < 0: r = sqrt2 * ((-1) ** m) * Ylm.imag elif m == 0: assert(Ylm.imag == 0.0) r = Ylm.real else: r = sqrt2 * ((-1) ** m) * Ylm.real R[ip, it] = pymath.fabs(r) if r < 0: C[ip, it] = 'y' else: C[ip, it] = 'b' X = R * numpy.sin(PHI) * numpy.cos(THETA) Y = R * numpy.sin(PHI) * numpy.sin(THETA) Z = R * numpy.cos(PHI) fig = plt.figure() ax = fig.add_subplot(1,1,1, projection='3d') plot = ax.plot_surface( X, Y, Z, rstride=1, cstride=1, facecolors=C, linewidth=0, antialiased=True, alpha=0.5) print 'Saving %s...' % ('ylm%d%d.png' % (l, m)) plt.savefig('ylm%d%d.png' % (l, m))
def evaluate_1degree(ClmList, png_filename): from scitbx import math from scitbx.array_family import flex import math as pymath import numpy d2r = pymath.pi / 180.0 order = order_from_nterm(len(ClmList)) lfg = math.log_factorial_generator(2 * order + 1) nsssphe = math.nss_spherical_harmonics(order,50000,lfg) Clm = { } idx = 0 for l in range(1, order+1): for m in range(-l, l+1): Clm[(l,m)] = ClmList[idx] idx += 1 abscor = numpy.empty((1+180//1, 1+360//1), float, 'C') sqrt2 = pymath.sqrt(2) for t in range(0, 181, 1): for p in range(0, 361, 1): a = 1.0 for l in range(1, order+1): for m in range(-l, l+1): # Ylm = nsssphe.spherical_harmonic(l, m, t*d2r, p*d2r) # Convert from complex to real according to # http://en.wikipedia.org/wiki/Spherical_harmonics#Real_form Ylm = nsssphe.spherical_harmonic(l, abs(m), t*d2r, p*d2r) if m < 0: a += Clm[(l,m)] * sqrt2 * ((-1) ** m) * Ylm.imag elif m == 0: assert(Ylm.imag == 0.0) a += Clm[(l,m)] * Ylm.real else: a += Clm[(l,m)] * sqrt2 * ((-1) ** m) * Ylm.real abscor[(t//1, p//1)] = a import matplotlib matplotlib.use('Agg') from matplotlib import pyplot plot = pyplot.imshow(abscor) pyplot.colorbar() pyplot.savefig(png_filename) return
def evaluate_1degree(ClmList): from scitbx import math from scitbx.array_family import flex import math as pymath import numpy d2r = pymath.pi / 180.0 order = order_from_nterm(len(ClmList)) lfg = math.log_factorial_generator(2 * order + 1) nsssphe = math.nss_spherical_harmonics(order, 50000, lfg) Clm = {} idx = 0 for l in range(1, order + 1): for m in range(-l, l + 1): Clm[(l, m)] = ClmList[idx] idx += 1 abscor = numpy.empty((1 + 180 // 1, 1 + 360 // 1), float, 'C') sqrt2 = pymath.sqrt(2) for t in range(0, 181, 1): for p in range(0, 361, 1): a = 1.0 for l in range(1, order + 1): for m in range(-l, l + 1): # Ylm = nsssphe.spherical_harmonic(l, m, t*d2r, p*d2r) # Convert from complex to real according to # http://en.wikipedia.org/wiki/Spherical_harmonics#Real_form Ylm = nsssphe.spherical_harmonic(l, abs(m), t * d2r, p * d2r) if m < 0: a += Clm[(l, m)] * sqrt2 * ((-1)**m) * Ylm.imag elif m == 0: assert (Ylm.imag == 0.0) a += Clm[(l, m)] * Ylm.real else: a += Clm[(l, m)] * sqrt2 * ((-1)**m) * Ylm.real abscor[(t // 1, p // 1)] = a return abscor
def plot_absorption_plots(physical_model, reflection_table=None): """Make a number of plots to help with the interpretation of the absorption correction.""" # First plot the absorption surface d = { "absorption_surface": { "data": [], "layout": { "title": "Absorption correction surface", "xaxis": { "domain": [0, 1], "anchor": "y", "title": "azimuthal angle (degrees)", }, "yaxis": { "domain": [0, 1], "anchor": "x", "title": "polar angle (degrees)", }, }, "help": absorption_help_msg, } } params = physical_model.components["absorption"].parameters order = int(-1.0 + ((1.0 + len(params)) ** 0.5)) lfg = scitbxmath.log_factorial_generator(2 * order + 1) STEPS = 50 azimuth_ = np.linspace(0, 2 * np.pi, 2 * STEPS) polar_ = np.linspace(0, np.pi, STEPS) THETA, _ = np.meshgrid(azimuth_, polar_, indexing="ij") lmax = int(-1.0 + ((1.0 + len(params)) ** 0.5)) Intensity = np.ones(THETA.shape) undiffracted_intensity = np.ones(THETA.shape) counter = 0 sqrt2 = math.sqrt(2) nsssphe = scitbxmath.nss_spherical_harmonics(order, 50000, lfg) for l in range(1, lmax + 1): for m in range(-l, l + 1): for it, t in enumerate(polar_): for ip, p in enumerate(azimuth_): Ylm = nsssphe.spherical_harmonic(l, abs(m), t, p) if m < 0: r = sqrt2 * ((-1) ** m) * Ylm.imag elif m == 0: assert Ylm.imag == 0.0 r = Ylm.real else: r = sqrt2 * ((-1) ** m) * Ylm.real Intensity[ip, it] += params[counter] * r # for the undiffracted intensity, we want to add the correction # at each point to the parity conjugate. We can use the fact # that the odd l terms are parity odd, and even are even, to # just calculate the even terms as follows if l % 2 == 0: undiffracted_intensity[ip, it] += params[counter] * r counter += 1 d["absorption_surface"]["data"].append( { "x": list(azimuth_ * 180.0 / np.pi), "y": list(polar_ * 180.0 / np.pi), "z": list(Intensity.T.tolist()), "type": "heatmap", "colorscale": "Viridis", "colorbar": {"title": "inverse <br>scale factor"}, "name": "absorption surface", "xaxis": "x", "yaxis": "y", } ) d["undiffracted_absorption_surface"] = { "data": [], "layout": { "title": "Undiffracted absorption correction", "xaxis": { "domain": [0, 1], "anchor": "y", "title": "azimuthal angle (degrees)", }, "yaxis": { "domain": [0, 1], "anchor": "x", "title": "polar angle (degrees)", }, }, "help": """ This plot shows the calculated relative absorption for a paths travelling straight through the crystal at a given direction in a crystal-fixed frame of reference (in spherical coordinates). This gives an indication of the effective shape of the crystal for absorbing x-rays. In this plot, the pole (polar angle 0) corresponds to the laboratory x-axis. """, } d["undiffracted_absorption_surface"]["data"].append( { "x": list(azimuth_ * 180.0 / np.pi), "y": list(polar_ * 180.0 / np.pi), "z": list(undiffracted_intensity.T.tolist()), "type": "heatmap", "colorscale": "Viridis", "colorbar": {"title": "inverse <br>scale factor"}, "name": "Undiffracted absorption correction", "xaxis": "x", "yaxis": "y", } ) if not reflection_table: return d # now plot the directions of the scattering vectors d["vector_directions"] = { "data": [], "layout": { "title": "Scattering vectors in crystal frame", "xaxis": { "domain": [0, 1], "anchor": "y", "title": "azimuthal angle (degrees)", "range": [0, 360], }, "yaxis": { "domain": [0, 1], "anchor": "x", "title": "polar angle (degrees)", "range": [0, 180], }, "coloraxis": { "showscale": False, }, }, "help": """ This plot shows the scattering vector directions in the crystal reference frame used to determine the absorption correction. The s0 vectors are plotted in yellow, the s1 vectors are plotted in teal. This gives an indication of which parts of the absorption correction surface are sampled when determining the absorption correction. In this plot, the pole (polar angle 0) corresponds to the laboratory x-axis.""", } STEPS = 180 # do one point per degree azimuth_ = np.linspace(0, 2 * np.pi, 2 * STEPS) polar_ = np.linspace(0, np.pi, STEPS) THETA, _ = np.meshgrid(azimuth_, polar_, indexing="ij") Intensity = np.full(THETA.shape, np.NAN) # note, the s1_lookup, s0_lookup is only calculated for large datasets, so # for small datasets we need to calculate again. if "s1_lookup" not in physical_model.components["absorption"].data: s1_lookup = calc_lookup_index( calc_theta_phi(reflection_table["s1c"]), points_per_degree=1 ) idx_polar, idx_azimuth = np.divmod(np.unique(s1_lookup), 360) Intensity[idx_azimuth, idx_polar] = 1 else: s1_lookup = np.unique(physical_model.components["absorption"].data["s1_lookup"]) # x is phi, y is theta idx_polar, idx_azimuth = np.divmod(s1_lookup, 720) idx_polar = idx_polar // 2 # convert from two points per degree to one idx_azimuth = idx_azimuth // 2 Intensity[idx_azimuth, idx_polar] = 1 d["vector_directions"]["data"].append( { "x": list(azimuth_ * 180.0 / np.pi), "y": list(polar_ * 180.0 / np.pi), "z": list(Intensity.T.tolist()), "type": "heatmap", "colorscale": "Viridis", "showscale": False, "xaxis": "x", "yaxis": "y", "zmin": 0, "zmax": 2, } ) Intensity = np.full(THETA.shape, np.NAN) if "s0_lookup" not in physical_model.components["absorption"].data: s0_lookup = calc_lookup_index( calc_theta_phi(reflection_table["s0c"]), points_per_degree=1 ) idx_polar, idx_azimuth = np.divmod(np.unique(s0_lookup), 360) Intensity[idx_azimuth, idx_polar] = 2 else: s0_lookup = np.unique(physical_model.components["absorption"].data["s0_lookup"]) # x is phi, y is theta idx_polar, idx_azimuth = np.divmod(s0_lookup, 720) idx_polar = idx_polar // 2 # convert from two points per degree to one idx_azimuth = idx_azimuth // 2 Intensity[idx_azimuth, idx_polar] = 2 d["vector_directions"]["data"].append( { "x": list(azimuth_ * 180.0 / np.pi), "y": list(polar_ * 180.0 / np.pi), "z": list(Intensity.T.tolist()), "type": "heatmap", "colorscale": "Viridis", "showscale": False, "xaxis": "x", "yaxis": "y", "zmin": 0, "zmax": 2, } ) scales = physical_model.components["absorption"].calculate_scales() hist = flex.histogram(scales, n_slots=min(100, int(scales.size() * 10))) d["absorption_corrections"] = { "data": [ { "x": list(hist.slot_centers()), "y": list(hist.slots()), "type": "bar", "name": "Applied absorption corrections", }, ], "layout": { "title": "Applied absorption corrections", "xaxis": {"anchor": "y", "title": "Inverse scale factor"}, "yaxis": {"anchor": "x", "title": "Number of reflections"}, }, } return d