-
Notifications
You must be signed in to change notification settings - Fork 30
Add tetrapod model implementation #705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9a7e0e1
40769a5
c995274
4399102
03c63fb
7037bea
2d70685
6a25247
f7de9f6
26b9812
6ce1b31
0e0450c
5faa83f
3ba6a03
08584bf
a1461ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // acos(-1.0/3.0)/2.0 = half of tetrahedral angle ~54.7356 deg | ||
| #define TETRAHEDRAL_HALF_ANGLE acos(-1.0 / 3.0) / 2.0 | ||
|
|
||
| static double u_n(int n, double theta, double alpha) { | ||
| const double phi[4] = {0.0, M_PI_2, M_PI, 3.0 * M_PI_2}; | ||
| const double sign[4] = {1.0, -1.0, 1.0, -1.0}; | ||
| return sign[n] * cos(TETRAHEDRAL_HALF_ANGLE) * cos(theta) + | ||
| sin(TETRAHEDRAL_HALF_ANGLE) * sin(theta) * cos(alpha - phi[n]); | ||
| } | ||
|
|
||
| // L: arm length, R: outer radius, t: shell thickness, R_c = R - t: core radius | ||
| static double Fq_n(double q, double u, double L, double R, double t, | ||
| double contrast_core, double contrast_shell) { | ||
| double R_c = R - t; | ||
| double quL2 = q * u * L * 0.5; | ||
| double mu = sqrt(fmax(0.0, 1.0 - u * u)); | ||
| double V_c = M_PI * R_c * R_c * L; | ||
| double V = M_PI * R * R * L; | ||
| return sas_sinx_x(quL2) * (contrast_core * V_c * sas_2J1x_x(q * mu * R_c) + | ||
| contrast_shell * V * sas_2J1x_x(q * mu * R)); | ||
| } | ||
|
|
||
| static double form_volume(double L, double R, double t) { | ||
| // V = 4 * pi * (R + t)^2 * L (outer volume of 4 arms) | ||
| return 4.0 * M_PI * (R + t) * (R + t) * L; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An overestimate of the volume since the ends of the cylinders overlap at the origin. The effect will be significant when the arms are short and wide.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for your reply. I agree with your point. However, the scattering model also ignores overlap effects, and any resulting artifacts appear only in the high-(q) region (provably?). Therefore, for now, I have followed the treatment used in the literature.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed that you don't need to do anything. The model corresponds to the published model. You may want to note in the documentation that the approximation is only valid for length >> radius+thickness. I'm a little concerned about the effects of volume normalization on low q values, but it may be that scattering is overestimated by as much as volume, and so errors cancel. We could use Monte Carlo sampling and the generic scattering calculator to confirm this. |
||
| } | ||
|
|
||
| static double radius_effective(int mode, double L, double R, double t) { | ||
| switch (mode) { | ||
| default: | ||
| case 1: // equivalent volume sphere | ||
| return cbrt(form_volume(L, R, t) / M_4PI_3); | ||
| case 2: // length of tetrapod arms (L) | ||
| return L; | ||
| } | ||
| } | ||
|
|
||
| static double Iq(double q, double L, double R, double t, double sld_core, | ||
| double sld_shell, double sld_solvent) { | ||
| double contrast_core = sld_core - sld_shell; | ||
| double contrast_shell = sld_shell - sld_solvent; | ||
| double total = 0.0; | ||
|
|
||
| for (int dtheta = 0; dtheta < GAUSS_N; dtheta++) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't use adaptive integration.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the suggestion. I understand that your comment refers to a more genuine adaptive integration scheme.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Target accuracy for the models is 5 digits of precision for SANS (R=20 nm at q=1/Å) and USANS (R=20 μm at q=0.002/Å). For USANS slit smearing, which pushes q out to 0.1/Å, the target accuracy 0.2. With regard to performance, I'm trying to limit it to 2 s to evaluate 200 q points on my mac M2 chip. I recently revised all the models to use a simple adaptive integration scheme (#658) based on qr max for the shape along the c-axis and in the a-b plane. For the outer loop I use the max of these two, but only the a-b cross section for the inner loop. I'm limiting the (θ,φ) grid to 100 000 evaluation points, with no more than 76 points in the outer loop. Here's an example from triaxial ellipsoid: const double qr_max_inner = fmax(q*radius_equat_minor, q*radius_equat_major);
const double qr_max = fmax(qr_max_inner, q*radius_polar);
constant double *z_outer, *w_outer;
constant double *z_inner, *w_inner;
int n_outer = gauss_weights(qr_max, ADAPTIVE_MAX_OUTER, &w_outer, &z_outer);
int n_inner = gauss_weights(qr_max_inner, n_outer, &w_inner, &z_inner);For the tetrapod, Code in This is not needed for this PR, but please convert this comment into a new ticket if you choose to do it later. [It is now in #705]
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @pkienzle is that change to adaptive integration in the master branch yet? or is it only in the |
||
| double theta = | ||
| M_PI_2 * (GAUSS_Z[dtheta] + 1.0); // map from [-1, 1] to [0, pi] | ||
| double w_theta = | ||
| GAUSS_W[dtheta] * M_PI_2; // adjust weight for the new range | ||
|
|
||
| double integral_alpha = 0.0; | ||
| for (int dalpha = 0; dalpha < GAUSS_N; dalpha++) { | ||
| double alpha = | ||
| M_PI * (GAUSS_Z[dalpha] + 1.0); // map from [-1, 1] to [0, 2*pi] | ||
| double w_alpha = | ||
| GAUSS_W[dalpha] * M_PI; // adjust weight for the new range | ||
|
|
||
| double u[4], F[4]; | ||
| for (int n = 0; n < 4; n++) { | ||
| u[n] = u_n(n, theta, alpha); | ||
| F[n] = Fq_n(q, u[n], L, R, t, contrast_core, contrast_shell); | ||
| } | ||
| double sum_arms = 0.0; | ||
| for (int n = 0; n < 4; n++) { | ||
| sum_arms += F[n] * F[n]; | ||
| for (int m = n + 1; m < 4; m++) { | ||
| sum_arms += 2.0 * F[n] * F[m] * cos(q * (u[n] - u[m]) * L / 2.0); | ||
| } | ||
| } | ||
| sum_arms *= sin(theta); | ||
| integral_alpha += sum_arms * w_alpha; | ||
| } | ||
| total += integral_alpha * w_theta; | ||
| } | ||
| return 1e-4 * total / (4.0 * M_PI); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| r""" | ||
| Definition | ||
| ---------- | ||
|
|
||
| Calculates the scattering from a tetrapod-shaped structure. A tetrapod consists | ||
| of four cylindrical arms radiating from a central point, oriented along the | ||
| (1,1,1), (-1,-1,1), (-1,1,-1), and (1,-1,-1) directions. | ||
|
|
||
| The scattering intensity is calculated as an average over all | ||
| orientations: | ||
|
|
||
| .. math:: | ||
|
|
||
| I(q) = \frac{(\Delta \rho)^2}{4\pi} | ||
| \int_0^{\pi} \int_0^{2\pi} | ||
| \left|\sum_{n=1}^{4} F_n(q, \theta, \varphi)\right|^2 | ||
| \sin\theta \, d\theta \, d\varphi | ||
|
|
||
| where $F_n$ is the core-shell form factor amplitude of the $n$-th arm: | ||
|
|
||
| .. math:: | ||
|
|
||
| F_n(q, \theta, \varphi) = \text{sinc}\!\left(\frac{q u_n L}{2}\right) | ||
| \left[ | ||
| (\rho_\text{core} - \rho_\text{shell})\, V_\text{core}\, | ||
| \frac{2 J_1(q \mu_n R_\text{core})}{q \mu_n R_\text{core}} | ||
| + (\rho_\text{shell} - \rho_\text{solvent})\, V_\text{shell}\, | ||
| \frac{2 J_1(q \mu_n R)}{q \mu_n R} | ||
| \right] | ||
|
|
||
| with $u_n = \hat{q} \cdot \hat{a}_n$, $\mu_n = \sqrt{1 - u_n^2}$, $L$ the arm | ||
| length, $R$ the arm outer radius, $R_\text{core} = R - t$ the core radius | ||
| ($t$ = shell thickness), and $V = \pi R^2 L$, $V_\text{core} = \pi R_\text{core}^2 L$. | ||
| Expanding the squared modulus into a double sum gives: | ||
|
|
||
| .. math:: | ||
|
|
||
| I(q) = \frac{1}{4\pi} | ||
| \int \sum_{n=1}^{4}\sum_{m=1}^{4} | ||
| F_n F_m \cos\!\left(\frac{q(u_n-u_m)L}{2}\right) | ||
| \sin\theta \, d\theta \, d\varphi | ||
|
|
||
| The cosine factor is the interference term between the centres of arms $n$ | ||
| and $m$, which are displaced by $\tfrac{L}{2}\hat{a}_n$ from the junction. | ||
|
|
||
| Geometry | ||
| -------- | ||
|
|
||
| The four arms are oriented along tetrahedral directions. With | ||
| $A = 109.5 /2$ (the half-angle between arms), the arm unit vectors and the | ||
| corresponding projections $u_n$ are | ||
|
|
||
| .. math:: | ||
|
|
||
| u_n = s_n \cos A \cos\theta + \sin A \sin\theta \cos(\varphi - \varphi_n) | ||
|
|
||
| where $(s_n, \varphi_n) = (+1,\ 0),\ (-1,\ \pi/2),\ (+1,\ \pi),\ (-1,\ 3\pi/2)$ | ||
| for $n = 1, 2, 3, 4$ respectively. | ||
|
|
||
| Each arm has length $L$, outer radius $R$, and shell thickness $t$. | ||
|
|
||
| References | ||
| ---------- | ||
|
|
||
| #. Seoki Kyoo Seo *Korean J. Chem. Eng.* 34(2017) 1192-1198 DOI:10.1007/s11814-016-0341-x | ||
|
|
||
| Authorship and Verification | ||
| ---------------------------- | ||
|
|
||
| * **Author:** Yuhei Yamada (Github user name: Indigo Carmine, https://orcid.org/0009-0003-9780-4135) | ||
| * **Last Modified by:** | ||
| """ | ||
|
|
||
| from numpy import inf | ||
|
|
||
| name = "tetrapod" | ||
| title = "Core-shell tetrapod with four cylindrical arms" | ||
| description = """ | ||
| Calculates the scattering from a core-shell tetrapod structure with four | ||
| cylindrical arms radiating from a central point. Each arm has a cylindrical | ||
| core and a coaxial shell. | ||
| """ | ||
| category = "shape:cylinder" | ||
|
|
||
| # [ "name", "units", default, [lower, upper], "type", "description"], | ||
| parameters = [ | ||
| ["length", "Ang", 1000, [0, inf], "volume", "Arm length"], | ||
| ["core_radius", "Ang", 50, [0, inf], "volume", "Arm core radius"], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are still subtracting thickness from radius internally, so this parameter is outer radius, not the core radius. To avoid confusion with core-shell models, I suggest changing the name to We are following the "mathematical" convention |
||
| ["thickness", "Ang", 10, [0, inf], "volume", "Arm shell thickness"], | ||
| ["sld_core", "1e-6/Ang^2", 1, [-inf, inf], "sld", "Arm core scattering length density"], | ||
| ["sld_shell", "1e-6/Ang^2", 0.5, [-inf, inf], "sld", "Arm shell scattering length density"], | ||
| ["sld_solvent", "1e-6/Ang^2", 0, [-inf, inf], "sld", "Solvent scattering length density"], | ||
| ] | ||
|
|
||
| source = ["lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", "tetrapod.c"] | ||
| have_Fq = False | ||
| opencl = True | ||
|
|
||
|
|
||
| radius_effective_modes = [ | ||
| "equivalent volume sphere", | ||
| "length of tetrapod arms (L)", | ||
| ] | ||
|
|
||
|
|
||
| test = [ | ||
| # thickness=0 reduces to uniform cylinder: contrast = sld_core - sld_solvent = 1 | ||
| [{"thickness": 0}, 1.099643429303014e-03, 2.746377685546875e03], | ||
| [{"thickness": 0}, 1.033727919136565e-01, 4.374285638332367e-01], | ||
| [{"thickness": 0}, 3.145051218117226e-01, 3.527995198965073e-03], | ||
| # default core-shell parameters | ||
| [{}, 1.099643429303014e-03, 1.846798618097820e03], | ||
| [{}, 1.033727919136565e-01, 1.811197691988146e-01], | ||
| [{}, 3.145051218117226e-01, 1.080396557066592e-03], | ||
| ] | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following can be precomputed:
cos_A = cos(TETRAHEDRAL_HALF_ANGLE) = √(1/3)
sin_A = sin(TETRAHEDRAL_HALF_ANGLE) = √(2/3)