source: sasmodels/explore/realspace.py @ 7d0afa3

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 7d0afa3 was 7d0afa3, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

explore/realspace: update monte carlo simulation with command line parameters

  • Property mode set to 100644
File size: 26.4 KB
Line 
1from __future__ import division, print_function
2
3import time
4from copy import copy
5import os
6import argparse
7from collections import OrderedDict
8
9import numpy as np
10from numpy import pi, radians, sin, cos, sqrt
11from numpy.random import poisson, uniform
12from numpy.polynomial.legendre import leggauss
13from scipy.integrate import simps
14from scipy.special import j1 as J1
15
16try:
17    import numba
18    USE_NUMBA = True
19except ImportError:
20    USE_NUMBA = False
21
22# Definition of rotation matrices comes from wikipedia:
23#    https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
24def Rx(angle):
25    """Construct a matrix to rotate points about *x* by *angle* degrees."""
26    a = radians(angle)
27    R = [[1, 0, 0],
28         [0, +cos(a), -sin(a)],
29         [0, +sin(a), +cos(a)]]
30    return np.matrix(R)
31
32def Ry(angle):
33    """Construct a matrix to rotate points about *y* by *angle* degrees."""
34    a = radians(angle)
35    R = [[+cos(a), 0, +sin(a)],
36         [0, 1, 0],
37         [-sin(a), 0, +cos(a)]]
38    return np.matrix(R)
39
40def Rz(angle):
41    """Construct a matrix to rotate points about *z* by *angle* degrees."""
42    a = radians(angle)
43    R = [[+cos(a), -sin(a), 0],
44         [+sin(a), +cos(a), 0],
45         [0, 0, 1]]
46    return np.matrix(R)
47
48def rotation(theta, phi, psi):
49    """
50    Apply the jitter transform to a set of points.
51
52    Points are stored in a 3 x n numpy matrix, not a numpy array or tuple.
53    """
54    return Rx(phi)*Ry(theta)*Rz(psi)
55
56def apply_view(points, view):
57    """
58    Apply the view transform (theta, phi, psi) to a set of points.
59
60    Points are stored in a 3 x n numpy array.
61
62    View angles are in degrees.
63    """
64    theta, phi, psi = view
65    return np.asarray((Rz(phi)*Ry(theta)*Rz(psi))*np.matrix(points.T)).T
66
67
68def invert_view(qx, qy, view):
69    """
70    Return (qa, qb, qc) for the (theta, phi, psi) view angle at detector
71    pixel (qx, qy).
72
73    View angles are in degrees.
74    """
75    theta, phi, psi = view
76    q = np.vstack((qx.flatten(), qy.flatten(), 0*qx.flatten()))
77    return np.asarray((Rz(-psi)*Ry(-theta)*Rz(-phi))*np.matrix(q))
78
79
80class Shape:
81    rotation = np.matrix([[1., 0, 0], [0, 1, 0], [0, 0, 1]])
82    center = np.array([0., 0., 0.])[:, None]
83    r_max = None
84
85    def volume(self):
86        # type: () -> float
87        raise NotImplementedError()
88
89    def sample(self, density):
90        # type: (float) -> np.ndarray[N], np.ndarray[N, 3]
91        raise NotImplementedError()
92
93    def rotate(self, theta, phi, psi):
94        self.rotation = rotation(theta, phi, psi) * self.rotation
95        return self
96
97    def shift(self, x, y, z):
98        self.center = self.center + np.array([x, y, z])[:, None]
99        return self
100
101    def _adjust(self, points):
102        points = np.asarray(self.rotation * np.matrix(points.T)) + self.center
103        return points.T
104
105    def r_bins(self, q, over_sampling=1, r_step=0.):
106        r_max = min(2 * pi / q[0], self.r_max)
107        if r_step == 0.:
108            r_step = 2 * pi / q[-1] / over_sampling
109        #r_step = 0.01
110        return np.arange(r_step, r_max, r_step)
111
112class Composite(Shape):
113    def __init__(self, shapes, center=(0, 0, 0), orientation=(0, 0, 0)):
114        self.shapes = shapes
115        self.rotate(*orientation)
116        self.shift(*center)
117
118        # Find the worst case distance between any two points amongst a set
119        # of shapes independent of orientation.  This could easily be a
120        # factor of two worse than necessary, e.g., a pair of thin rods
121        # end-to-end vs the same pair side-by-side.
122        distances = [((s1.r_max + s2.r_max)/2
123                      + sqrt(np.sum((s1.center - s2.center)**2)))
124                     for s1 in shapes
125                     for s2 in shapes]
126        self.r_max = max(distances + [s.r_max for s in shapes])
127
128    def volume(self):
129        return sum(shape.volume() for shape in self.shapes)
130
131    def sample(self, density):
132        values, points = zip(*(shape.sample(density) for shape in self.shapes))
133        return np.hstack(values), self._adjust(np.vstack(points))
134
135class Box(Shape):
136    def __init__(self, a, b, c,
137                 value, center=(0, 0, 0), orientation=(0, 0, 0)):
138        self.value = np.asarray(value)
139        self.rotate(*orientation)
140        self.shift(*center)
141        self.a, self.b, self.c = a, b, c
142        self._scale = np.array([a/2, b/2, c/2])[None, :]
143        self.r_max = sqrt(a**2 + b**2 + c**2)
144
145    def volume(self):
146        return self.a*self.b*self.c
147
148    def sample(self, density):
149        num_points = poisson(density*self.a*self.b*self.c)
150        points = self._scale*uniform(-1, 1, size=(num_points, 3))
151        values = self.value.repeat(points.shape[0])
152        return values, self._adjust(points)
153
154class EllipticalCylinder(Shape):
155    def __init__(self, ra, rb, length,
156                 value, center=(0, 0, 0), orientation=(0, 0, 0)):
157        self.value = np.asarray(value)
158        self.rotate(*orientation)
159        self.shift(*center)
160        self.ra, self.rb, self.length = ra, rb, length
161        self._scale = np.array([ra, rb, length/2])[None, :]
162        self.r_max = sqrt(4*max(ra, rb)**2 + length**2)
163
164    def volume(self):
165        return pi*self.ra*self.rb*self.length
166
167    def sample(self, density):
168        # density of the bounding box
169        num_points = poisson(density*4*self.ra*self.rb*self.length)
170        points = uniform(-1, 1, size=(num_points, 3))
171        radius = points[:, 0]**2 + points[:, 1]**2
172        points = self._scale*points[radius <= 1]
173        values = self.value.repeat(points.shape[0])
174        return values, self._adjust(points)
175
176class TriaxialEllipsoid(Shape):
177    def __init__(self, ra, rb, rc,
178                 value, center=(0, 0, 0), orientation=(0, 0, 0)):
179        self.value = np.asarray(value)
180        self.rotate(*orientation)
181        self.shift(*center)
182        self.ra, self.rb, self.rc = ra, rb, rc
183        self._scale = np.array([ra, rb, rc])[None, :]
184        self.r_max = 2*max(ra, rb, rc)
185
186    def volume(self):
187        return 4*pi/3 * self.ra * self.rb * self.rc
188
189    def sample(self, density):
190        # randomly sample from a box of side length 2*r, excluding anything
191        # not in the ellipsoid
192        num_points = poisson(density*8*self.ra*self.rb*self.rc)
193        points = uniform(-1, 1, size=(num_points, 3))
194        radius = np.sum(points**2, axis=1)
195        points = self._scale*points[radius <= 1]
196        values = self.value.repeat(points.shape[0])
197        return values, self._adjust(points)
198
199class Helix(Shape):
200    def __init__(self, helix_radius, helix_pitch, tube_radius, tube_length,
201                 value, center=(0, 0, 0), orientation=(0, 0, 0)):
202        self.value = np.asarray(value)
203        self.rotate(*orientation)
204        self.shift(*center)
205        self.helix_radius, self.helix_pitch = helix_radius, helix_pitch
206        self.tube_radius, self.tube_length = tube_radius, tube_length
207        helix_length = helix_pitch * tube_length/sqrt(helix_radius**2 + helix_pitch**2)
208        self.r_max = sqrt((2*helix_radius + 2*tube_radius)*2
209                          + (helix_length + 2*tube_radius)**2)
210
211    def volume(self):
212        # small tube radius approximation; for larger tubes need to account
213        # for the fact that the inner length is much shorter than the outer
214        # length
215        return pi*self.tube_radius**2*self.tube_length
216
217    def points(self, density):
218        num_points = poisson(density*4*self.tube_radius**2*self.tube_length)
219        points = uniform(-1, 1, size=(num_points, 3))
220        radius = points[:, 0]**2 + points[:, 1]**2
221        points = points[radius <= 1]
222
223        # Based on math stackexchange answer by Jyrki Lahtonen
224        #     https://math.stackexchange.com/a/461637
225        # with helix along z rather than x [so tuples in answer are (z, x, y)]
226        # and with random points in the cross section (p1, p2) rather than
227        # uniform points on the surface (cos u, sin u).
228        a, R = self.tube_radius, self.helix_radius
229        h = self.helix_pitch
230        scale = 1/sqrt(R**2 + h**2)
231        t = points[:, 3] * (self.tube_length * scale/2)
232        cos_t, sin_t = cos(t), sin(t)
233
234        # rx = R*cos_t
235        # ry = R*sin_t
236        # rz = h*t
237        # nx = -a * cos_t * points[:, 1]
238        # ny = -a * sin_t * points[:, 1]
239        # nz = 0
240        # bx = (a * h/scale) * sin_t * points[:, 2]
241        # by = (-a * h/scale) * cos_t * points[:, 2]
242        # bz = a*R/scale
243        # x = rx + nx + bx
244        # y = ry + ny + by
245        # z = rz + nz + bz
246        u, v = (R - a*points[:, 1]), (a * h/scale)*points[:, 2]
247        x = u * cos_t + v * sin_t
248        y = u * sin_t - v * cos_t
249        z = a*R/scale + h * t
250
251        points = np.hstack((x, y, z))
252        values = self.value.repeat(points.shape[0])
253        return values, self._adjust(points)
254
255def csbox(a=10, b=20, c=30, da=1, db=2, dc=3, slda=1, sldb=2, sldc=3, sld_core=4):
256    core = Box(a, b, c, sld_core)
257    side_a = Box(da, b, c, slda, center=((a+da)/2, 0, 0))
258    side_b = Box(a, db, c, sldb, center=(0, (b+db)/2, 0))
259    side_c = Box(a, b, dc, sldc, center=(0, 0, (c+dc)/2))
260    side_a2 = copy(side_a).shift(-a-da, 0, 0)
261    side_b2 = copy(side_b).shift(0, -b-db, 0)
262    side_c2 = copy(side_c).shift(0, 0, -c-dc)
263    shape = Composite((core, side_a, side_b, side_c, side_a2, side_b2, side_c2))
264    return shape
265
266def _Iqxy(values, x, y, z, qa, qb, qc):
267    """I(q) = |sum V(r) rho(r) e^(1j q.r)|^2 / sum V(r)"""
268    Iq = [abs(np.sum(values*np.exp(1j*(qa_k*x + qb_k*y + qc_k*z))))**2
269            for qa_k, qb_k, qc_k in zip(qa.flat, qb.flat, qc.flat)]
270    return Iq
271
272if USE_NUMBA:
273    # Override simple numpy solution with numba if available
274    from numba import njit
275    @njit("f8[:](f8[:],f8[:],f8[:],f8[:],f8[:],f8[:],f8[:])")
276    def _Iqxy_jit(values, x, y, z, qa, qb, qc):
277        Iq = np.zeros_like(qa)
278        for j in range(len(Iq)):
279            total = 0. + 0j
280            for k in range(len(Iq)):
281                total += values[k]*np.exp(1j*(qa[j]*x[k] + qb[j]*y[k] + qc[j]*z[k]))
282            Iq[j] = abs(total)**2
283        return Iq
284
285def calc_Iqxy(qx, qy, rho, points, volume=1.0, view=(0, 0, 0)):
286    qx, qy = np.broadcast_arrays(qx, qy)
287    qa, qb, qc = invert_view(qx, qy, view)
288    rho, volume = np.broadcast_arrays(rho, volume)
289    values = rho*volume
290    x, y, z = points.T
291
292    # I(q) = |sum V(r) rho(r) e^(1j q.r)|^2 / sum V(r)
293    Iq = _Iqxy(values, x, y, z, qa.flatten(), qb.flatten(), qc.flatten())
294    return np.asarray(Iq).reshape(qx.shape) / np.sum(volume)
295
296def _calc_Pr_nonuniform(r, rho, points):
297    # Make Pr a little be bigger than necessary so that only distances
298    # min < d < max end up in Pr
299    n_max = len(r)+1
300    extended_Pr = np.zeros(n_max+1, 'd')
301    # r refers to bin centers; find corresponding bin edges
302    bins = bin_edges(r)
303    t_next = time.clock() + 3
304    for k, rho_k in enumerate(rho[:-1]):
305        distance = sqrt(np.sum((points[k] - points[k+1:])**2, axis=1))
306        weights = rho_k * rho[k+1:]
307        index = np.searchsorted(bins, distance)
308        # Note: indices may be duplicated, so "Pr[index] += w" will not work!!
309        extended_Pr += np.bincount(index, weights, n_max+1)
310        t = time.clock()
311        if t > t_next:
312            t_next = t + 3
313            print("processing %d of %d"%(k, len(rho)-1))
314    Pr = extended_Pr[1:-1]
315    return Pr
316
317def _calc_Pr_uniform(r, rho, points):
318    # Make Pr a little be bigger than necessary so that only distances
319    # min < d < max end up in Pr
320    dr, n_max = r[0], len(r)
321    extended_Pr = np.zeros(n_max+1, 'd')
322    t0 = time.clock()
323    t_next = t0 + 3
324    for k, rho_k in enumerate(rho[:-1]):
325        distances = sqrt(np.sum((points[k] - points[k+1:])**2, axis=1))
326        weights = rho_k * rho[k+1:]
327        index = np.minimum(np.asarray(distances/dr, 'i'), n_max)
328        # Note: indices may be duplicated, so "Pr[index] += w" will not work!!
329        extended_Pr += np.bincount(index, weights, n_max+1)
330        t = time.clock()
331        if t > t_next:
332            t_next = t + 3
333            print("processing %d of %d"%(k, len(rho)-1))
334    #print("time py:", time.clock() - t0)
335    Pr = extended_Pr[:-1]
336    # Make Pr independent of sampling density.  The factor of 2 comes because
337    # we are only accumulating the upper triangular distances.
338    #Pr = Pr * 2 / len(rho)**2
339    return Pr
340
341    # Can get an additional 2x by going to C.  Cuda/OpenCL will allow even
342    # more speedup, though still bounded by the n^2 cose.
343    """
344void pdfcalc(int n, const double *pts, const double *rho,
345             int nPr, double *Pr, double rstep)
346{
347  int i,j;
348
349  for (i=0; i<n-2; i++) {
350    for (j=i+1; j<=n-1; j++) {
351      const double dxx=pts[3*i]-pts[3*j];
352      const double dyy=pts[3*i+1]-pts[3*j+1];
353      const double dzz=pts[3*i+2]-pts[3*j+2];
354      const double d=sqrt(dxx*dxx+dyy*dyy+dzz*dzz);
355      const int k=rint(d/rstep);
356      if (k < nPr) Pr[k]+=rho[i]*rho[j];
357    }
358  }
359}
360"""
361
362if USE_NUMBA:
363    # Override simple numpy solution with numba if available
364    @njit("f8[:](f8[:], f8[:], f8[:,:])")
365    def _calc_Pr_uniform(r, rho, points):
366        dr = r[0]
367        n_max = len(r)
368        Pr = np.zeros_like(r)
369        for j in range(len(rho) - 1):
370            x, y, z = points[j, 0], points[j, 1], points[j, 2]
371            for k in range(j+1, len(rho)):
372                distance = sqrt((x - points[k, 0])**2
373                                + (y - points[k, 1])**2
374                                + (z - points[k, 2])**2)
375                index = int(distance/dr)
376                if index < n_max:
377                    Pr[index] += rho[j] * rho[k]
378        # Make Pr independent of sampling density.  The factor of 2 comes because
379        # we are only accumulating the upper triangular distances.
380        #Pr = Pr * 2 / len(rho)**2
381        return Pr
382
383
384def calc_Pr(r, rho, points):
385    # P(r) with uniform steps in r is 3x faster; check if we are uniform
386    # before continuing
387    if np.max(np.abs(np.diff(r) - r[0])) > r[0]*0.01:
388        Pr = _calc_Pr_nonuniform(r, rho, points)
389    else:
390        Pr = _calc_Pr_uniform(r, rho, points)
391    return Pr / Pr.max()
392
393
394def j0(x):
395    return np.sinc(x/np.pi)
396
397def calc_Iq(q, r, Pr):
398    Iq = np.array([simps(Pr * j0(qk*r), r) for qk in q])
399    #Iq = np.array([np.trapz(Pr * j0(qk*r), r) for qk in q])
400    Iq /= Iq[0]
401    return Iq
402
403# NOTE: copied from sasmodels/resolution.py
404def bin_edges(x):
405    """
406    Determine bin edges from bin centers, assuming that edges are centered
407    between the bins.
408
409    Note: this uses the arithmetic mean, which may not be appropriate for
410    log-scaled data.
411    """
412    if len(x) < 2 or (np.diff(x) < 0).any():
413        raise ValueError("Expected bins to be an increasing set")
414    edges = np.hstack([
415        x[0]  - 0.5*(x[1]  - x[0]),  # first point minus half first interval
416        0.5*(x[1:] + x[:-1]),        # mid points of all central intervals
417        x[-1] + 0.5*(x[-1] - x[-2]), # last point plus half last interval
418        ])
419    return edges
420
421# -------------- plotters ----------------
422def plot_calc(r, Pr, q, Iq, theory=None):
423    import matplotlib.pyplot as plt
424    plt.subplot(211)
425    plt.plot(r, Pr, '-', label="Pr")
426    plt.xlabel('r (A)')
427    plt.ylabel('Pr (1/A^2)')
428    plt.subplot(212)
429    plt.loglog(q, Iq, '-', label='from Pr')
430    plt.xlabel('q (1/A')
431    plt.ylabel('Iq')
432    if theory is not None:
433        plt.loglog(theory[0], theory[1]/theory[1][0], '-', label='analytic')
434        plt.legend()
435
436def plot_calc_2d(qx, qy, Iqxy, theory=None):
437    import matplotlib.pyplot as plt
438    qx, qy = bin_edges(qx), bin_edges(qy)
439    #qx, qy = np.meshgrid(qx, qy)
440    if theory is not None:
441        plt.subplot(121)
442    plt.pcolormesh(qx, qy, np.log10(Iqxy))
443    plt.xlabel('qx (1/A)')
444    plt.ylabel('qy (1/A)')
445    if theory is not None:
446        plt.subplot(122)
447        plt.pcolormesh(qx, qy, np.log10(theory))
448        plt.xlabel('qx (1/A)')
449
450def plot_points(rho, points):
451    import mpl_toolkits.mplot3d
452    import matplotlib.pyplot as plt
453
454    ax = plt.axes(projection='3d')
455    try:
456        ax.axis('square')
457    except Exception:
458        pass
459    n = len(points)
460    #print("len points", n)
461    index = np.random.choice(n, size=500) if n > 500 else slice(None, None)
462    ax.scatter(points[index, 0], points[index, 1], points[index, 2], c=rho[index])
463    #low, high = points.min(axis=0), points.max(axis=0)
464    #ax.axis([low[0], high[0], low[1], high[1], low[2], high[2]])
465    ax.autoscale(True)
466
467# ----------- Analytic models --------------
468def sas_sinx_x(x):
469    with np.errstate(all='ignore'):
470        retvalue = sin(x)/x
471    retvalue[x == 0.] = 1.
472    return retvalue
473
474def sas_2J1x_x(x):
475    with np.errstate(all='ignore'):
476        retvalue = 2*J1(x)/x
477    retvalue[x == 0] = 1.
478    return retvalue
479
480def sas_3j1x_x(x):
481    """return 3*j1(x)/x"""
482    with np.errstate(all='ignore'):
483        retvalue = 3*(sin(x) - x*cos(x))/x**3
484    retvalue[x == 0.] = 1.
485    return retvalue
486
487def cylinder_Iq(q, radius, length):
488    z, w = leggauss(76)
489    cos_alpha = (z+1)/2
490    sin_alpha = sqrt(1.0 - cos_alpha**2)
491    Iq = np.empty_like(q)
492    for k, qk in enumerate(q):
493        qab, qc = qk*sin_alpha, qk*cos_alpha
494        Fq = sas_2J1x_x(qab*radius) * sas_sinx_x(qc*length/2)
495        Iq[k] = np.sum(w*Fq**2)
496    Iq = Iq/Iq[0]
497    return Iq
498
499def cylinder_Iqxy(qx, qy, radius, length, view=(0, 0, 0)):
500    qa, qb, qc = invert_view(qx, qy, view)
501    qab = sqrt(qa**2 + qb**2)
502    Fq = sas_2J1x_x(qab*radius) * sas_sinx_x(qc*length/2)
503    Iq = Fq**2
504    return Iq.reshape(qx.shape)
505
506def sphere_Iq(q, radius):
507    Iq = sas_3j1x_x(q*radius)**2
508    return Iq/Iq[0]
509
510def box_Iq(q, a, b, c):
511    z, w = leggauss(76)
512    outer_sum = np.zeros_like(q)
513    for cos_alpha, outer_w in zip((z+1)/2, w):
514        sin_alpha = sqrt(1.0-cos_alpha*cos_alpha)
515        qc = q*cos_alpha
516        siC = c*sas_sinx_x(c*qc/2)
517        inner_sum = np.zeros_like(q)
518        for beta, inner_w in zip((z + 1)*pi/4, w):
519            qa, qb = q*sin_alpha*sin(beta), q*sin_alpha*cos(beta)
520            siA = a*sas_sinx_x(a*qa/2)
521            siB = b*sas_sinx_x(b*qb/2)
522            Fq = siA*siB*siC
523            inner_sum += inner_w * Fq**2
524        outer_sum += outer_w * inner_sum
525    Iq = outer_sum / 4  # = outer*um*zm*8.0/(4.0*M_PI)
526    return Iq/Iq[0]
527
528def box_Iqxy(qx, qy, a, b, c, view=(0, 0, 0)):
529    qa, qb, qc = invert_view(qx, qy, view)
530    sia = sas_sinx_x(qa*a/2)
531    sib = sas_sinx_x(qb*b/2)
532    sic = sas_sinx_x(qc*c/2)
533    Fq = sia*sib*sic
534    Iq = Fq**2
535    return Iq.reshape(qx.shape)
536
537def csbox_Iq(q, a, b, c, da, db, dc, slda, sldb, sldc, sld_core):
538    z, w = leggauss(76)
539
540    sld_solvent = 0
541    overlapping = False
542    dr0 = sld_core - sld_solvent
543    drA, drB, drC = slda-sld_solvent, sldb-sld_solvent, sldc-sld_solvent
544    tA, tB, tC = a + 2*da, b + 2*db, c + 2*dc
545
546    outer_sum = np.zeros_like(q)
547    for cos_alpha, outer_w in zip((z+1)/2, w):
548        sin_alpha = sqrt(1.0-cos_alpha*cos_alpha)
549        qc = q*cos_alpha
550        siC = c*sas_sinx_x(c*qc/2)
551        siCt = tC*sas_sinx_x(tC*qc/2)
552        inner_sum = np.zeros_like(q)
553        for beta, inner_w in zip((z + 1)*pi/4, w):
554            qa, qb = q*sin_alpha*sin(beta), q*sin_alpha*cos(beta)
555            siA = a*sas_sinx_x(a*qa/2)
556            siB = b*sas_sinx_x(b*qb/2)
557            siAt = tA*sas_sinx_x(tA*qa/2)
558            siBt = tB*sas_sinx_x(tB*qb/2)
559            if overlapping:
560                Fq = (dr0*siA*siB*siC
561                      + drA*(siAt-siA)*siB*siC
562                      + drB*siAt*(siBt-siB)*siC
563                      + drC*siAt*siBt*(siCt-siC))
564            else:
565                Fq = (dr0*siA*siB*siC
566                      + drA*(siAt-siA)*siB*siC
567                      + drB*siA*(siBt-siB)*siC
568                      + drC*siA*siB*(siCt-siC))
569            inner_sum += inner_w * Fq**2
570        outer_sum += outer_w * inner_sum
571    Iq = outer_sum / 4  # = outer*um*zm*8.0/(4.0*M_PI)
572    return Iq/Iq[0]
573
574def csbox_Iqxy(qx, qy, a, b, c, da, db, dc, slda, sldb, sldc, sld_core, view=(0,0,0)):
575    qa, qb, qc = invert_view(qx, qy, view)
576
577    sld_solvent = 0
578    overlapping = False
579    dr0 = sld_core - sld_solvent
580    drA, drB, drC = slda-sld_solvent, sldb-sld_solvent, sldc-sld_solvent
581    tA, tB, tC = a + 2*da, b + 2*db, c + 2*dc
582    siA = a*sas_sinx_x(a*qa/2)
583    siB = b*sas_sinx_x(b*qb/2)
584    siC = c*sas_sinx_x(c*qc/2)
585    siAt = tA*sas_sinx_x(tA*qa/2)
586    siBt = tB*sas_sinx_x(tB*qb/2)
587    siCt = tC*sas_sinx_x(tC*qc/2)
588    Fq = (dr0*siA*siB*siC
589          + drA*(siAt-siA)*siB*siC
590          + drB*siA*(siBt-siB)*siC
591          + drC*siA*siB*(siCt-siC))
592    Iq = Fq**2
593    return Iq.reshape(qx.shape)
594
595# --------- Test cases -----------
596
597def check_cylinder(radius=25, length=125, rho=2.):
598    shape = EllipticalCylinder(radius, radius, length, rho)
599    fn = lambda q: cylinder_Iq(q, radius, length)*rho**2
600    fn_xy = lambda qx, qy, view: cylinder_Iqxy(qx, qy, radius, length, view=view)*rho**2
601    return shape, fn, fn_xy
602
603def check_cylinder_lattice(radius=25, length=125, rho=2.):
604    nx, dx = 1, 2*radius
605    ny, dy = 30, 2*radius
606    nz, dz = 30, length
607    dx, dy, dz = 2*dx, 2*dy, 2*dz
608    def center(*args):
609        sigma = 0.333
610        space = 2
611        return [(space*n+np.random.randn()*sigma)*x for n, x in args]
612    t0 = time.time()
613    shapes = [EllipticalCylinder(radius, radius, length, rho,
614                                 #center=(ix*dx, iy*dy, iz*dz)
615                                 orientation=np.random.randn(3)*0,
616                                 center=center((ix, dx), (iy, dy), (iz, dz))
617                                )
618              for ix in range(nx)
619              for iy in range(ny)
620              for iz in range(nz)]
621    shape = Composite(shapes)
622    print("generate points time", time.time() - t0)
623    fn = None
624    fn_xy = lambda qx, qy, view: cylinder_Iqxy(qx, qy, radius, length, view=view)
625    return shape, fn, fn_xy
626
627def check_sphere(radius=125, rho=2):
628    shape = TriaxialEllipsoid(radius, radius, radius, rho)
629    fn = lambda q: cylinder_Iq(q, radius, length)*rho**2
630    fn_xy = lambda qx, qy, view: sphere_Iq(np.sqrt(qx**2+qy**2), radius)*rho**2
631    return shape, fn, fn_xy
632
633def check_box(a=10, b=20, c=30, rho=2.):
634    shape = Box(a, b, c, rho)
635    fn = lambda q: box_Iq(q, a, b, c)*rho**2
636    fn_xy = lambda qx, qy, view: box_Iqxy(qx, qy, a, b, c, view=view)*rho**2
637    return shape, fn, fn_xy
638
639def check_box_lattice(a=10, b=20, c=30, rho=2.):
640    nx, dx = 3, a
641    ny, dy = 5, b
642    nz, dz = 5, c
643    dx, dy, dz = 2*dx, 2*dy, 2*dz
644    def center(*args):
645        sigma = 0.333
646        space = 2
647        return [(space*n+np.random.randn()*sigma)*x for n, x in args]
648    t0 = time.time()
649    shapes = [Box(a, b, c, rho,
650                  #center=(ix*dx, iy*dy, iz*dz)
651                  orientation=np.random.randn(3)*10,
652                  center=center((ix, dx), (iy, dy), (iz, dz))
653                 )
654              for ix in range(nx)
655              for iy in range(ny)
656              for iz in range(nz)]
657    shape = Composite(shapes)
658    print("generate points time", time.time() - t0)
659    fn = None
660    fn_xy = lambda qx, qy, view: box_Iqxy(qx, qy, a, b, c, view=view)
661    return shape, fn, fn_xy
662
663
664def check_csbox(a=10, b=20, c=30, da=1, db=2, dc=3, slda=1, sldb=2, sldc=3, sld_core=4):
665    shape = csbox(a, b, c, da, db, dc, slda, sldb, sldc, sld_core)
666    fn = lambda q: csbox_Iq(q, a, b, c, da, db, dc, slda, sldb, sldc, sld_core)
667    fn_xy = lambda qx, qy, view: csbox_Iqxy(qx, qy, a, b, c, da, db, dc,
668                                            slda, sldb, sldc, sld_core, view=view)
669    return shape, fn, fn_xy
670
671
672SHAPE_FUNCTIONS = OrderedDict([
673    ("cylinder", check_cylinder),
674    ("sphere", check_sphere),
675    ("box", check_box),
676    ("csbox", check_csbox),
677    ("multicyl", check_cylinder_lattice),
678    ("multibox", check_box_lattice),
679])
680SHAPES = list(SHAPE_FUNCTIONS.keys())
681
682def check_shape(title, shape, fn=None, show_points=False,
683                mesh=100, qmax=1.0, r_step=0.01, samples=5000):
684    rho_solvent = 0
685    qmin = qmax/1000.
686    q = np.logspace(np.log10(qmin), np.log10(qmax), mesh)
687    r = shape.r_bins(q, r_step=r_step)
688    sampling_density = samples / shape.volume()
689    rho, points = shape.sample(sampling_density)
690    t0 = time.time()
691    Pr = calc_Pr(r, rho-rho_solvent, points)
692    print("calc Pr time", time.time() - t0)
693    Iq = calc_Iq(q, r, Pr)
694    theory = (q, fn(q)) if fn is not None else None
695
696    import pylab
697    if show_points:
698         plot_points(rho, points); pylab.figure()
699    plot_calc(r, Pr, q, Iq, theory=theory)
700    pylab.gcf().canvas.set_window_title(title)
701    pylab.show()
702
703def check_shape_2d(title, shape, fn=None, view=(0, 0, 0), show_points=False,
704                   mesh=100, qmax=1.0, samples=5000):
705    rho_solvent = 0
706    qx = np.linspace(0.0, qmax, mesh)
707    qy = np.linspace(0.0, qmax, mesh)
708    Qx, Qy = np.meshgrid(qx, qy)
709    sampling_density = samples / shape.volume()
710    #t0 = time.time()
711    rho, points = shape.sample(sampling_density)
712    #print("sample time", time.time() - t0)
713    t0 = time.time()
714    Iqxy = calc_Iqxy(Qx, Qy, rho, points, view=view)
715    print("calc Iqxy time", time.time() - t0)
716    theory = fn(Qx, Qy, view) if fn is not None else None
717    Iqxy += 0.001 * Iqxy.max()
718    if theory is not None:
719        theory += 0.001 * theory.max()
720
721    import pylab
722    if show_points:
723        plot_points(rho, points); pylab.figure()
724    plot_calc_2d(qx, qy, Iqxy, theory=theory)
725    pylab.gcf().canvas.set_window_title(title)
726    pylab.show()
727
728def main():
729    parser = argparse.ArgumentParser(
730        description="Compute scattering from realspace sampling",
731        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
732        )
733    parser.add_argument('-d', '--dim', type=int, default=1, help='dimension 1 or 2')
734    parser.add_argument('-m', '--mesh', type=int, default=100, help='number of mesh points')
735    parser.add_argument('-s', '--samples', type=int, default=5000, help="number of sample points")
736    parser.add_argument('-q', '--qmax', type=float, default=0.5, help='max q')
737    parser.add_argument('-v', '--view', type=str, default='0,0,0', help='theta,phi,psi angles')
738    parser.add_argument('-p', '--plot', action='store_true', help='plot points')
739    parser.add_argument('shape', choices=SHAPES, nargs='?', default=SHAPES[0], help='oriented shape')
740    parser.add_argument('pars', type=str, nargs='*', help='shape parameters')
741    opts = parser.parse_args()
742    pars = {key: float(value) for p in opts.pars for key, value in [p.split('=')]}
743    shape, fn, fn_xy = SHAPE_FUNCTIONS[opts.shape](**pars)
744    title = "%s(%s)" % (opts.shape, " ".join(opts.pars))
745    if opts.dim == 1:
746        check_shape(title, shape, fn, show_points=opts.plot,
747                    mesh=opts.mesh, qmax=opts.qmax, samples=opts.samples)
748    else:
749        view = tuple(float(v) for v in opts.view.split(','))
750        check_shape_2d(title, shape, fn_xy, view=view, show_points=opts.plot,
751                       mesh=opts.mesh, qmax=opts.qmax, samples=opts.samples)
752
753
754if __name__ == "__main__":
755    main()
Note: See TracBrowser for help on using the repository browser.