Source code for jscatter.formfactor.polymer

# -*- coding: utf-8 -*-
# written by Ralf Biehl at the Forschungszentrum Jülich ,
# Jülich Center for Neutron Science 1 and Institute of Complex Systems 1
#    Jscatter is a program to read, analyse and plot data
#    Copyright (C) 2015-2024  Ralf Biehl
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import inspect
import os

import math
import numpy as np
import scipy
import scipy.integrate
import scipy.special as special

from .. import formel
from ..dataarray import dataArray as dA


__all__ = ['powerLaw', 'guinier', 'genGuinier', 'beaucage', 'guinierPorod3d', 'guinierPorod', 'gaussianChain',
           'ringPolymer', 'wormlikeChain', 'alternatingCoPolymer', 'ornsteinZernike', 'DAB', 'polymerCorLength']

_path_ = os.path.realpath(os.path.dirname(__file__))

# variable to allow printout for debugging as if debug:print 'message'
debug = False


def _gammainc(a, x):
    """Incomplete Gamma function"""
    return special.gammainc(a, x)*special.gamma(a)


[docs] def powerLaw(q, d, A): """ Power law function / Porod scattering .. math:: F(q) = Aq^{-d} Parameters ---------- q : array Wavevector in 1/nm. d : float Power law exponent or Porod exponent. A : float Amplitude Returns ------- dataArray Notes ----- For flat interfaces (surfaces) Porod predict a :math:`q^{-4}` behaviour at larger q (only this is Porod scattering). This was later extended to rough surfaces by Sinha et al.[1]_ for fractal surfaces. Similar is found for mass fractal aggregates. The power law function is often used to represent the high q scattering representing different structures when the Q range to low Q is limited that details for more specific models are missing. The different power laws are connected to : - d = 5/3-2 swollen polymer chains, - d = 2 ideal Gaussian chains - d > 2 compacted polymer chain - d = 3 globular e.g. collapsed chains. (volume scattering) - d = 4 surface scattering at a sharp interface/surface (Porod scattering) - d = 6-dim rough surface area with a dimensionality dim between 2-3 (rough surface) - d < 3 mass fractals The naming of the power law regions is not clear to me (different from the Guinier region which is well defined). While the Porod law for flat surfaces is d=4, sometimes its called the Porod exponent [2]_ or fractal exponent. A Porod region is connected to a region with d=4 or the region of Porod analysis (plot I*q^4 vs. q) and for d<4 a fractal region is observed. Often Porod region is used as synonymous for "high Q power law region" which is not necessarily connected to Porod scattering. For q=0 the function is undefined References ---------- .. [1] X-ray and neutron scattering from rough surfaces S. K. Sinha, E. B. Sirota, S. Garoff, and H. B. Stanley Phys. Rev. B 38, 2297 (1988) https://doi.org/10.1103/PhysRevB.38.2297 .. [2] Analysis of the Beaucage model Boualem Hammouda J. Appl. Cryst. (2010). 43, 1474–1478 http://dx.doi.org/10.1107/S0021889810033856 """ q = np.atleast_1d(q) result = dA(np.c_[q, A*q**(-d)].T) result.setColumnIndex(iey=None) result.columnname = 'q; Fq' result.exponent = d result.A = A result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def guinier(q, Rg=1, A=1): """ Classical Guinier :math:`I(q) = A e^{-Rg^2q^2/3}` see genGuinier with alpha=0 Parameters ---------- q :array A : float Rg : float """ return genGuinier(q, Rg=Rg, A=A, alpha=0)
[docs] def genGuinier(q, Rg=1, A=1, alpha=0): r""" Generalized Guinier approximation for low wavevector q scattering q*Rg< 1-1.3 For absolute scattering see introduction :ref:`formfactor (ff)`. Parameters ---------- q : array of float Wavevector Rg : float Radius of gyration in units=1/q alpha : float Shape [α = 0] spheroid, [α = 1] rod-like [α = 2] plane A : float Amplitudes Returns ------- dataArray Columns [q,Fq] Notes ----- Quantitative analysis of particle size and shape starts with the Guinier approximations. - For three-dimensional objects the Guinier approximation is given by :math:`I(q) = A e^{-Rg^2q^2/3}` - This approximation can be extended also to rod-like and plane objects by :math:`I(q) =(\alpha \pi q^{-\alpha}) A e^{-Rg^2q^2/(3-\alpha) }` If the particle has one dimension of length L that is much larger than the others (i.e., elongated, rod-like, or worm-like), then there is a q range such that qR_c < 1 << qL, where α = 1. Examples -------- :: import jscatter as js import numpy as np q=js.loglist(0.01,5,300) spheroid=js.ff.genGuinier(q, Rg=2, A=1, alpha=0) rod=js.ff.genGuinier(q, Rg=2, A=1, alpha=1) plane=js.ff.genGuinier(q, Rg=2, A=1, alpha=2) p=js.grace() p.plot(spheroid,le='sphere') p.plot(rod,le='rod') p.plot(plane,le='plane') p.yaxis(scale='l',min=1e-4,max=1e4) p.xaxis(scale='l') p.legend(x=0.03,y=0.1) #p.save(js.examples.imagepath+'/genGuinier.jpg') .. image:: ../../examples/images/genGuinier.jpg :align: center :width: 50 % :alt: genGuinier References ---------- .. [1] Form and structure of self-assembling particles in monoolein-bile salt mixtures Rex P. Hjelm, Claudio Schteingart, Alan F. Hofmann, and Devinderjit S. Sivia J. Phys. Chem., 99:16395--16406, 1995 """ q = np.atleast_1d(q) if alpha == 0: pre = 1 elif alpha == 1 or alpha == 2: pre = alpha * np.pi * q ** -alpha else: raise TypeError('alpha needs to be in 0,1,2') I = pre * A * np.exp(-Rg ** 2 * q ** 2 / (3 - alpha)) result = dA(np.c_[q, I].T) result.setColumnIndex(iey=None) result.columnname = 'q; Fq' result.Rg = Rg result.A = A result.alpha = alpha result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def beaucage(q, Rg=1, G=1, d=3): r""" Beaucage introduced a model based on the polymer fractal model. Beaucage used the numerical integration form (Benoit, 1957) although the analytical integral form was available [1]_. This is an artificial connection of Guinier and Porod Regime . Better use the polymer fractal model [1]_ used in gaussianChain. For absolute scattering see introduction :ref:`formfactor (ff)`. Parameters ---------- q : array Wavevector Rg : float Radius of gyration in 1/q units G : float Guinier scaling factor, transition between Guinier and Porod d : float Power law exponent for large wavevectors Returns ------- dataArray Columns [q,Fq] Notes ----- Equation 9+10 in [1]_ .. math:: I(q) &= G e^{-q^2 R_g^2 / 3.} + C q^{-d} \left[erf(qR_g / 6^{0.5})\right]^{3d} C &= \frac{G d}{R_g^d} \left[\frac{6d^2}{(2+d)(2+2d)}\right]^{d / 2.} \Gamma(d/2) with the Gamma function :math:`\Gamma(x)` . Various structures are related to the power law : - d = 5/3 fully swollen chains, - d = 2 ideal Gaussian chains and - d = 3 globular e.g. collapsed chains. (volume scattering) - d = 4 surface scattering at a sharp interface/surface (Porod scattering) - d = 6-dim rough surface area with a dimensionality dim between 2-3 (rough surface) - d < 3 mass fractals (eg gaussian chain dim = 2) The Beaucage model is used to analyze small-angle scattering (SAS) data from fractal and particulate systems. It models the Guinier and Porod regions with a smooth transition between them and yields a radius of gyration and a Porod exponent. This model is an approximate form of an earlier polymer fractal model that has been generalized to cover a wider scope. The practice of allowing both the Guinier and the Porod scale factors to vary independently during nonlinear least-squares fits introduces undesired artefact's in the fitting of SAS data to this model. Examples -------- :: import jscatter as js import numpy as np q=js.loglist(0.1,5,300) d2=js.ff.beaucage(q, Rg=2, d=2) d3=js.ff.beaucage(q, Rg=2, d=3) d4=js.ff.beaucage(q, Rg=2,d=4) p=js.grace() p.plot(d2,le='d=2 gaussian chain') p.plot(d3,le='d=3 globular') p.plot(d4,le='d=4 sharp surface') p.yaxis(scale='l',min=1e-4,max=5) p.xaxis(scale='l') p.legend(x=0.15,y=0.1) #p.save(js.examples.imagepath+'/beaucage.jpg') .. image:: ../../examples/images/beaucage.jpg :align: center :width: 50 % :alt: beaucage .. [1] Analysis of the Beaucage model Boualem Hammouda J. Appl. Cryst. (2010). 43, 1474–1478 http://dx.doi.org/10.1107/S0021889810033856 """ q = np.atleast_1d(q) Rg = float(Rg) C = G * d / Rg ** d * (6 * d ** 2 / ((2. + d) * (2. + 2. * d))) ** (d / 2.) * special.gamma(d / 2.) I = G * np.exp(-q ** 2 * Rg ** 2 / 3.) + C / q ** d * (special.erf(q * Rg / 6 ** 0.5)) ** (3 * d) I[q == 0] = 1 result = dA(np.c_[q, I].T) result.setColumnIndex(iey=None) result.columnname = 'q; Fq' result.GuinierScalingfactor = G result.GuinierDimension = d result.Rg = Rg result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def guinierPorod3d(q, Rg1, s1, Rg2, s2, G2, dd): r""" Generalized Guinier-Porod Model with high Q power law with 3 length scales. An empirical model connecting the Guinier model with a transition to Porod scattering at high Q. The model represents the most general case containing three Guinier regions [1]_. Parameters ---------- q : float Wavevector in units of 1/nm Rg1 : float Radii of gyration for the short size of scattering object in units nm. Rg2 : float Radii of gyration for the overall size of scattering object in units nm. s1 : float Dimensionality parameter for the short size of scattering object (s1=1 for a cylinder) s2 : float Dimensionality parameter for the overall size of scattering object (s2=0 for a cylinder) G2 : float Intensity for q=0. d : float Porod exponent Returns ------- dataArray Columns [q,Iq] Iq scattering intensity Notes ----- Equ. 5 in [1]_ as: .. math:: I(Q) &= \frac{G_2}{Q^{s_2}} exp\big(\frac{-Q^2R_{g2}^2}{3-s_2}\big) \; for Q \leq Q_2 I(Q) &= \frac{G_1}{Q^{s_1}} exp\big(\frac{-Q^2R_{g1}^2}{3-s_1}\big) \; for Q_2 \leq Q \leq Q_1 I(Q) &= \frac{D}{Q^d} \; for Q \geq Q_1 with equ 4 .. math:: Q_1 &= \frac{1}{R_{g1}} \big( \frac{(d-s_1)(3-s_1)}{2} \big)^{1/2} D &= G_1 exp(\frac{-Q_1^2R_{g1}^2}{3-s_1})Q_1^{d-s_1} Q_2 &= \big[frac{s_1-s_2}{\frac{2}{3-s_2}R_{g2}^2 - \frac{2}{3-s_1}R_{g1}^2 } \big]^{1/2} G_2 &= G_1 exp\big[ -Q_2^2 \big(\frac{R_{g1}^2}{3-s_1} - \frac{R_{g2}^2}{3-s_2} \big) \big] Q_2^{s_2-s_1} For fitting limit parameters to :math:`3>s_1>s_2` and :math:`R_{g2} >R_{g1}`. For more details see [1]_ For a cylinder with length L and radius R (see [1]_) :math:`R_{g2} = (L^2/12+R^2/2)^{\frac{1}{2}}` and :math:`R_{g1}=R/\sqrt{2}` Examples -------- :: import jscatter as js q=js.loglist(0.01,5,300) I=js.ff.guinierPorod3d(q,Rg1=1,s1=1,Rg2=10,s2=0,G2=1,dd=4) p=js.grace() p.plot(I) p.xaxis(scale='l',label='q / nm\S-1') p.yaxis(scale='l',label='I(q) / a.u.') #p.save(js.examples.imagepath+'/guinierPorod3d.jpg') .. image:: ../../examples/images/guinierPorod3d.jpg :align: center :width: 50 % :alt: guinierPorod3d References ---------- .. [1] A new Guinier/Porod Model B. Hammouda J. Appl. Cryst. (2010) 43, 716-719 Author M. Kruteva JCNS 2019 """ q = np.atleast_1d(q) # define parameters for smooth transitions Q1 = (1 / Rg1) * ((dd - s1) * (3 - s1) / 2) ** 0.5 Q2 = ((s1 - s2) / (2 / (3 - s2) * Rg2 ** 2 - 2 / (3 - s1) * Rg1 ** 2)) ** 0.5 G1 = G2 / (np.exp(-Q2 ** 2 * (Rg1 ** 2 / (3 - s1) - Rg2 ** 2 / (3 - s2))) * Q2 ** (s2 - s1)) D = G1 * np.exp(-Q1 ** 2 * Rg1 ** 2 / (3 - s1)) * Q1 ** (dd - s1) # define functions in different regions def _I1_3regions(q): res = G2 / q ** s2 * np.exp(-q ** 2 * Rg2 ** 2 / (3 - s2)) return res def _I2_3regions(q): res = G1 / q ** s1 * np.exp(-q ** 2 * Rg1 ** 2 / (3 - s1)) return res def _I3_3regions(q): res = D / q ** dd return res I = np.piecewise(q, [q < Q2, (Q2 <= q) & (q < Q1), q >= Q1], [_I1_3regions, _I2_3regions, _I3_3regions]) result = dA(np.c_[q, I].T) result.columnname = 'q; Iq' result.setColumnIndex(iey=None) result.Rg1 = Rg1 result.s1 = s1 result.Rg2 = Rg2 result.s2 = s2 result.G1 = G1 result.G2 = G2 result.dd = dd result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def guinierPorod(q, Rg, s, I0, d): r""" Generalized Guinier-Porod Model with high Q power law. An empirical model connecting the Guinier model with a transition to Porod scattering at high Q. Parameters ---------- q : float Wavevector in units of 1/nm Rg : float Radii of gyration in units nm. s : float Dimensionality parameter describing the low Q region. - 0 spheres globular - 1 rods, linear - 2 lamella planar d : float Porod exponent describing the high Q slope. I0 : float Intensity, named G in [1]_. Returns ------- dataArray Columns [q, Iq] Iq scattering intensity Notes ----- Equ. 3 in [1]_ as: .. math:: I(Q) &= \frac{G}{Q^s}exp\big(\frac{-Q^2R_g^2}{3-s}\big) \; for Q \leq Q_1 I(Q) &= \frac{D}{Q^d} \; for Q \geq Q_1 with equ 4 .. math:: Q_1 &= \frac{1}{R_g} \big( \frac{(d-s)(3-s)}{2} \big)^{1/2} D &= G exp(\frac{-Q_1^2R_g^2}{3-s})Q_1^{d-s} Examples -------- :: import jscatter as js q=js.loglist(0.01,5,300) I=js.ff.guinierPorod(q,s=0,Rg=5,I0=1,d=4) p=js.grace() p.plot(I) p.xaxis(scale='l',label='q / nm\S-1') p.yaxis(scale='l',label='I(q) / a.u.') #p.save(js.examples.imagepath+'/guinierPorod.jpg') .. image:: ../../examples/images/guinierPorod.jpg :align: center :width: 50 % :alt: guinierPorod References ---------- .. [1] A new Guinier/Porod Model B. Hammouda J. Appl. Cryst. (2010) 43, 716-719 Author M. Kruteva JCNS 2019 """ q = np.atleast_1d(q) # define parameters for smooth transitions Q1 = (1 / Rg) * ((d - s) * (3 - s) / 2) ** 0.5 D = I0 * np.exp(-Q1 ** 2 * Rg ** 2 / (3 - s)) * Q1 ** (d - s) # define functions in different regions def _I1_2regions(q): res = I0 / q ** s * np.exp(-q ** 2 * Rg ** 2 / (3 - s)) return res def _I2_2regions(q): res = D / q ** d return res I = np.piecewise(q, [q < Q1, q >= Q1], [_I1_2regions, _I2_2regions]) result = dA(np.c_[q, I].T) result.columnname = 'q; Iq' result.setColumnIndex(iey=None) result.Rg = Rg result.s = s result.I0 = I0 result.D = D result.d = d result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def gaussianChain(q, Rg, nu=0.5): r""" General formfactor of a gaussian polymer chain with excluded volume parameter. For nu=0.5 this is the Debye model for Gaussian chain in theta solvent. nu>0.5 for good solvents,nu<0.5 for bad solvents. For absolute scattering see introduction :ref:`formfactor (ff)`. Parameters ---------- q : array Scattering vector, unit e.g. 1/A or 1/nm Rg : float Radius of gyration, units in 1/unit(q) nu : float, default=0.5 ν is the excluded volume parameter, which is related to the Porod exponent d as ν = 1/d and [5/3 <= d <= 3]. - fully swollen chains ν = 3/5 (good solvent) - for Gaussian chains ν = 1/2 (theta solvent) - collapsed chains ν = 1/3 (bad solvent) Returns ------- dataArray - Columns [q,Fq] - .radiusOfGyration - .nu excluded volume parameter Notes ----- - :math:`R_g^2=\frac{l^2 N^{2\nu}}{(2\nu+1)(2\nu+2)}` with monomer length l and monomer number N. - With :math:`U=Q^2l^2N^{2\nu}/6 =Q^2R_g^2(2\nu+1)(2\nu+2)/6` and :math:`\gamma_{inc}` as lower incomplete gamma function .. math:: F(Q) = \frac{1}{\nu U^{\frac{1}{2\nu}}} \gamma_{inc}(\frac{1}{2\nu}, U) - \frac{1}{\nu U^{\frac{1}{\nu}}} \gamma_{inc}(\frac{1}{\nu}, U) For :math:`\nu=0.5` this yields the Debye function .. math:: F(Q) = 2\frac{exp(-U)-1+U}{U^2} with :math:`U=(qR_g)^2` . - The absolute scattering is proportional to :math:`b^2 N^2=b^2 (R_g/l)^{1/\nu}` with monomer number :math:`N` and monomer scattering length :math:`b`. - From [1]_: "Note that this model describing polymer chains with excluded volume applies only in the mass fractal range ([5/3 <= d <= 3]) and does not apply to surface fractals ([3 < d < 4]). It does not reproduce the rigid-rod limit (d = 1) because it assumes chain flexibility from the outset, nor does it describe semi-flexible chains ([1 < d < 5/3]). " - This model should be favoured compared to the Beaucage model as it is not an artificial connection between two regimes. Examples -------- :: import jscatter as js import numpy as np q=js.loglist(0.1,8,100) p=js.grace() for nu in np.r_[0.3:0.61:0.05]: iq=js.ff.gaussianChain(q,2,nu) p.plot(iq,le='nu= $nu') p.yaxis(label='I(q)',scale='l',min=1e-3,max=1) p.xaxis(label='q / nm\S-1',scale='l') p.legend(x=0.2,y=0.1) p.title('Gaussian chains') #p.save(js.examples.imagepath+'/gaussianChain.jpg') .. image:: ../../examples/images/gaussianChain.jpg :align: center :width: 50 % :alt: gaussianChain References ---------- .. [1] Analysis of the Beaucage model Boualem Hammouda J. Appl. Cryst. (2010). 43, 1474–1478 http://dx.doi.org/10.1107/S0021889810033856 .. [2] SANS from homogeneous polymer mixtures: A unified overview. Hammouda, B. in Polymer Characteristics 87–133 (Springer-Verlag, 1993). doi:10.1007/BFb0025862 """ q = np.atleast_1d(q) if nu==0.5: # 10 times faster U = q ** 2 * Rg ** 2 # Debye function gu = lambda x: 2*(np.exp(-x)-1+x)/x**2 else: nu2 = nu * 2. U = q ** 2 * Rg ** 2 * (nu2 + 1) * (nu2 + 2) / 6. gu = lambda x: 1 / (nu * x ** (1. / nu2)) * _gammainc(1 / nu2, x) - 1 / (nu * x ** (1. / nu)) * _gammainc(1 / nu, x) res = np.piecewise(U, [U == 0], [1, gu]) result = dA(np.c_[q, res].T) result.setColumnIndex(iey=None) result.columnname = 'q; Fq' result.radiusOfGyration = Rg result.nu = nu result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def ringPolymer(q, N=None, a=None, Rg=None, nu=0.5): r""" General formfactor of a polymer ring with excluded volume effects. Parameters ---------- q : array Scattering vector, unit e.g. 1/A or 1/nm a : float, default =1 Segment length in nm. N : integer Number of segments Rg : float Radius of gyration, units in 1/unit(q) nu : float, default=0.5 ν is the excluded volume parameter, which is related to the Porod exponent d as ν = 1/d and [5/3 <= d <= 3]. - fully swollen chains ν = 3/5 (good solvent) - for Gaussian chains ν = 1/2 (theta solvent) - collapsed chains ν = 1/3 (bad solvent) Returns ------- dataArray Columns [q,Fq] - .segmentlength segment length a - .N number of segments - .Rg radius of gyration - .nu excluded volume parameter Notes ----- Equ 52 in [1]_ .. math:: F(q) = 2\int_0^1 ds(1-s) e^{-q^2a^2N^{2\nu}s^{2\nu}(1-s^{2\nu})/6} with :math:`R_g^2=\frac{a^2N^{2\nu}}{2}\frac{3\nu}{1+7\nu+14\nu^2+8\nu^3}` For nu=0.5 equ. 3.5 in [2]_ shows in short form related to the Dawson function .. math:: S(Q) = dawsn(U)/U = \frac{e^{-U^2}}{U} \int_0^U e^{t^2} with :math:`U=(q^2R_g^2)^{1/2}/2` for Rg from the linear chain (not Rg of the ring!). Examples -------- The excluded volume effects with :math:`\nu \neq 0.5` lead to increase/decrease at high q in the Kratky representation compared to nu=0.5 of a theta solvent. :: import jscatter as js import numpy as np q=js.loglist(0.03,8,100) p=js.grace(1.4,1) p.multi(1,2) for nu in [0.4,0.45,0.5,0.55,0.6]: iq=js.ff.ringPolymer(q,Rg=5,nu=nu) print(iq.N) p[0].plot(iq,le=f'nu={nu:.2f} N={iq.N:.0f}') p[1].plot(iq.X,iq.Y*iq.X**2,le=f'{nu}') p[0].yaxis(scale='l',label='I(q) / a.u.') p[0].xaxis(scale='l',label='q / nm\S-1') p[1].yaxis(scale='l',label=[r'I(q)q\S2\N / a.u.',1,'opposite'],ticklabel=['power',0,1,'opposite'],min=1e-2,max=0.2) p[1].xaxis(scale='l',label='q / nm\S-1') p[0].legend(x=0.03,y=0.01) p[1].subtitle('Kratky plot') p[0].title('ring polymer') p[0].subtitle('Rg = 5 nm, a=1') #p.save(js.examples.imagepath+'/ringPolymer.jpg') .. image:: ../../examples/images/ringPolymer.jpg :align: center :width: 50 % :alt: ringPolymer References ---------- .. [1] Form Factors for Branched Polymers with Excluded Volume Boualem Hammouda Journal of Research of the National Institute of Standards and Technology 121,139 (2016) http://dx.doi.org/10.6028/jres.121.006 .. [2] Some statistical properties of flexible ring polymers Edward F. Casassa JOURNAL OF POLYMER SCIENCE: PART A, 3, 605-614 (1965) https://doi.org/10.1002/pol.1965.100030217 """ # equ 52 in http://dx.doi.org/10.6028/jres.121.006 nu2 = nu*2 f = 3 * nu / (1 + 7 * nu + 14 * nu ** 2 + 8 * nu ** 3) if a is None: a=1 if Rg is None: Rg = (a**2*N**nu2/2*f)**0.5 elif N is None: N = (2*Rg**2/a**2/f)**(1/nu2) else: raise Exception('Missing value! Either Rg or N must be given!') def _integrand(qq,s): ss=s[:, None] return 2*(1-ss)* np.exp(-qq**2*a**2*N**nu2/6*ss**nu2*(1-ss**nu2)) res, err = formel.pQACC(_integrand, 0, 1, 's', qq=q) result = dA(np.c_[q, res].T) result.setColumnIndex(iey=None) result.columnname = 'q; Fq' result.Rg = Rg result.N = N result.segmentlength = a result.nu = nu result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def wormlikeChain(q, N, a, R=None, SLD=1, solventSLD=0, rtol=0.02): r""" Scattering of a wormlike chain, which correctly reproduces the rigid-rod and random-coil limits. To calculate the scattering of the classical Kratky-Porod model of semiflexible chains we use an analytical solution for arbitrary stiffness and length as given by Kholodenko [1]_. The transition from infinite thin chain to a cross sectional scattering uses the decoupling approximation and the scattering cross section of an infinitly thin (optional multishell)disc. Parameters ---------- q : array Wavevectors in 1/nm. N : float Length of the chain in units nm. Number of chain segments is N/l=N/(2a). We follow here the notation of [1]_. a : float Persistence length *a* with Kuhn length l=2a (segment length), units of nm. R : float Radius in units of nm. SLD : float Scattering length density segments. solventSLD : Solvent scattering length density. rtol : float Maximum relative tolerance in integration. Returns ------- dataArray Columns [q, Iq] - .chainRadius - .chainLength - .persistenceLength - .Rg - .volume - .contrast - .mu from :math:`Re =\sqrt{6} R_g = l (N/l)^\nu` For R=0 the normalized formfactor is returned. Notes ----- We use equation 17 of [1]_ to calculate the normalized formfactor *S(q)* of a semiflexible thin polymer chain as it correctly recovers the limit of rigid rod and flexible chain (for details see [1]_). .. math:: S_{wc}(q) =& \frac{2}{x}[I_1(x)-\frac{1}{x}I_2(x)] with\; I_n =& \int_0^x z^{n-1}f(z) \; and \; x=\frac{3N}{2a} k<=& 3/2a : \; f(z) = \frac{1}{E} \frac{sinh(Ez)}{sinh(z)} k>& 3/2a : \; f(z) = \frac{1}{\overline{E}} \frac{sinh(\overline{E}z)}{sinh(z)} E^2 =& 1-(\frac{2}{3}ak)^2 ;\; \overline{E}^2 = 1-(\frac{2}{3}ak)^2 If the contour length is much larger than the cross section :math:`N>>R` then the cross section scattering can be separated. Within a decoupling approximation [4]_ we may use an infinitly thin disc formfactor :math:`S_{disc}(q)` oriented perpendicular to the chain. This can be calculated as homogeneous thin disc (included in using R>0) or as multi shell disc using *multiShellDisc* (see third example). .. math:: F(q) = S_{wc}(q,R=0,...) N S_{disc}(q,D=0,alpha=\pi/2,...) The forward scattering :math:`I_0` for a homogeneous cylinder is :math:`I_0=V^2(SLD-solventSLD)^2` with :math:`V=\pi R^2 N`. For multishellDisc(..,D=0,alpha=\pi/2,..).I0 the result has to be multiplied by the contour length *N*. .Rg is calculated according to equ 20 in [2]_ and similar is found in [3]_ with l=2a. .. math:: R_g^2 = \frac{lN}{6}\big( 1-\frac{3l}{2N}+\frac{3l^2}{2N^2}-\frac{3l^3}{4N^3}(1-e^{-2N/l}) \big) From [1]_ : The Kratky plot (Figure 4 ) is not the most convenient way to determine *a* as was pointed out in ref 20. Figure 5 provides an alternative way of measuring a by plotting the experimentally measurable combination Nk2S(k) versus a for fixed wavelength k. As Figure 5 indicates, this plot is rather insensitive to the chain length N and therefore is universal. The numerical analysis of eq 17 shows that this remains true for as long as k is not too small. Taking into account that the excluded-volume effects leave S(k) practically unchanged (e.g., see Figures 2 and 4 of ref 231, the plot of Figure 5 can serve as a useful alternative to the Kratky plot which, in addition, does not suffer from the polydispersity effects Examples -------- Kratky and S(q) representation of the wormlike chain model showing the transition due to longer linear segments. The longer the chain segments are the transition from Gaussian like :math:`~k^{-2}` to local linear :math:`~k^{-1}` shifts to smaller Q. The overall chain stays Gaussian like with a Flory size exponent :math:`\nu \approx 0.5` . :: import jscatter as js p=js.grace() p.multi(2,1) p.title('figure 3 (2 scaled) of ref Kholodenko Macromolecules 26, 4179 (1993)',size=1) q=js.loglist(0.01,10,100) for a in [0.3,1,2.5,5,20,50]: ff=js.ff.wormlikeChain(q,200,a) p[0].plot(ff.X,200*ff.Y*ff.X**2,legend=f'a={ff.persistenceLength:.4g}; Rg={ff.Rg:.2g}; '+r'\xn\f{}='+f'{ff.mu:.2g}') p[1].plot(ff.X,ff.Y,legend=f'a={ff.persistenceLength:.4g}; Rg={ff.Rg:.2g}') p[0].legend(x=11,y=30) p[0].yaxis(label=r'Nk\S2\NS(k)') p[0].xaxis(label='') p[1].xaxis(label='k',scale='l') p[1].yaxis(label='S(k)',scale='l') p[1].text(r'~k\S-1',x=1,y=0.005) p[1].text(r'~k\S-2',x=1,y=0.15) #p.save(js.examples.imagepath+'/wormlikeChain.jpg') .. image:: ../../examples/images/wormlikeChain.jpg :align: center :width: 50 % :alt: wormlikeChain A rescaled version according to fig 3 of [1]_ :: import jscatter as js p=js.grace() p.multi(2,1) p.title('figure 4 of ref Kholodenko Macromolecules 26, 4179 (1993)',size=1) # fig 4 seems to be wrong scale in [1]_ as for large N with a=1 fig 2 and 4 should have same plateau. a=1 q=js.loglist(0.01,4./a,100) for NN in [1,20,50,150,500]: ff=js.ff.wormlikeChain(q,NN,a) p[0].plot(ff.X*a,NN*a*ff.Y*ff.X**2,legend='N=%.4g' %ff.chainLength) p[1].plot(ff.X,ff.Y,legend='a=%.4g' %ff.persistenceLength) p[0].legend() p[0].yaxis(label=r'(N/a)(ka)\S2\NS(k)') p[0].xaxis(label='ka') p[1].xaxis(label='k',scale='l') p[1].yaxis(label='S(k)',scale='l') **Micellar wormlike structure** with core shell disc cross section. Instead of a core-shell disc multiShellDisc may approximate any radial distribution. :: import jscatter as js import numpy as np def thickworm(q, N, a, Rcore, shellD, SLDcore=1, SLDshell=2, solventSLD=0): worm = js.ff.wormlikeChain(q, N, a, R=0) cross = js.ff.multiShellDisc(q, radialthickness=[Rcore,shellD], shellthickness=[0,0], shellSLD=[SLDcore,SLDshell], solventSLD=solventSLD, alpha=np.pi/2) worm.Y = worm.Y*N*cross.Y worm.volume = N*np.pi*(Rcore+shellD)**2 worm.I0 = cross.I0*N return worm p=js.grace(1,0.7) p.title('Thick wormlike chain with coreshell cross section',size=1.5) p.subtitle('persistence length *a*') q=js.loglist(0.01,4,200) for a in [1,2.5,5,20,50]: ff=thickworm(q,N=200,a=a, Rcore=3, shellD=1, SLDcore=0, SLDshell=1) p.plot(ff.X,ff.Y,legend=f'a={ff.persistenceLength:.4g}; Rg={ff.Rg:.2g}') p.legend(x=0.03,y=1000) p.xaxis(label='q',scale='l',charsize=1.5) p.yaxis(label='S(q)',scale='l',charsize=1.5,min=1,max=3e5) #p.save(js.examples.imagepath+'/wormlikeChain2.jpg') .. image:: ../../examples/images/wormlikeChain2.jpg :align: center :width: 50 % :alt: worm References ---------- .. [1] Analytical calculation of the scattering function for polymers of arbitrary flexibility using the dirac propagator A. L. Kholodenko, Macromolecules, 26:4179--4183, 1993 .. [2] The structure factor of a wormlike chain and the random-phase-approximation solution for the spinodal line of a diblock copolymer melt Zhang X et. al. Soft Matter 10, 5405 (2014), https://doi.org/10.1039/C4SM00374H .. [3] Models of Polymer Chains Teraoka I. in Polymer Solutions: An Introduction to Physical Properties pp: 1-67, New York, John Wiley & Sons, Inc. https://doi.org/10.1002/0471224510.ch1 Decoupling approximation for cross section .. [4] Static structure factor of polymerlike micelles:Overall dimension, flexibility, and local properties of lecithin reverse micelles in deuterated isooctane Götz Jerke, Jan Skov Pedersen, Stefan Ulrich Egelhaaf, and Peter Schurtenberger Phys. Rev. E 56, 5772 ; https://doi.org/10.1103/PhysRevE.56.5772 """ a2 = 2. * float(abs(a)) # Kuhn length q = np.atleast_1d(q) # row vector limit = 100 # limit to avoid exp overflow x = 3 * N / a2 z = np.c_[0:x:1000 * 1j] # column vector EF = np.sqrt(np.sign((a2 * q / 3.)**2 - 1) * ((a2 * q / 3.) ** 2 - 1)) EFiszero = (EF == 0) EF[EFiszero] = 1 # to avoid EF=0 # fz is [ z , q ] matrix def FZ(qq, zz): mfz = np.zeros((zz.shape[0], qq.shape[0])) # now fill it mfz[(0 < zz) & (zz < limit) & (a2 * qq <= 3)] = ( np.sinh(zz[(0 < zz) & (zz < limit), None] * EF[None, a2 * qq <= 3]) / np.sinh( zz[(0 < zz) & (zz < limit), None]) / EF[None, a2 * qq <= 3]).flatten() # for to large zz we avoid expz>limit and use sinh(EF*zz)/sinh(zz)=exp(zz*(Ef-1)) for zz>limit mfz[(zz >= limit) & (a2 * qq <= 3)] = ( np.exp(zz[zz >= limit, None] * (EF[None, a2 * qq <= 3] - 1)) / EF[None, a2 * qq <= 3]).flatten() mfz[(0 < zz) & (zz < limit) & (a2 * qq > 3)] = ( np.sin(zz[(0 < zz) & (zz < limit), None] * EF[None, a2 * qq > 3]) / np.sinh( zz[(0 < zz) & (zz < limit), None]) / EF[None, a2 * qq > 3]).flatten() # mfz[(zz>limit ) & (a2*qq >3)] = 0 # default is zero # for zz=0 limes is 1 in both cases mfz[zz[:, 0] == 0, :] = 1 if np.any(EFiszero): # catch fz when EF is zero and assigned correct value mfz[(0 < zz) & (zz < limit) & EFiszero] = ( zz[(0 < zz) & (zz < limit)] / np.sinh(zz[(0 < zz) & (zz < limit)])) return mfz # integrate I1 and I2 from above matrix fz = FZ(q, z) I1 = scipy.integrate.simps(fz, z, axis=0) I2 = scipy.integrate.simps(fz * z, z, axis=0) P0 = 2. / x * (I1 - I2 / x) while True: # adaptive integration to increase accuracy stepwise nz = np.c_[0:x:(2 * len(z) - 1) * 1j] nfz = np.zeros((nz.shape[0], q.shape[0])) nfz[::2, :] = fz nfz[1::2, :] = FZ(q, nz[1::2]) # each second is new element I1 = scipy.integrate.simps(nfz, nz, axis=0) I2 = scipy.integrate.simps(nfz * nz, nz, axis=0) nP0 = 2. / x * (I1 - I2 / x) if max(abs(nP0 - P0) / abs(nP0)) < rtol or z.shape[0] >100000: P0 = nP0 break else: z = nz fz = nfz P0 = nP0 # now do the volume and sld if R: # not None or >0 Pcs = (2 * special.j1(q * R) / q / R) ** 2 V = np.pi * R * R * N sld = SLD - solventSLD I0 = V ** 2 * sld ** 2 else: Pcs = 1 R = 0 V = 1 sld = 1 I0 = 1 result = dA(np.c_[q, V ** 2 * sld ** 2 * P0 * Pcs].T) result.setColumnIndex(iey=None) result.columnname = 'q; Iq' result.chainRadius = R result.chainLength = N result.I0 = I0 result.persistenceLength = a # in [2]_ a is Kuhn length (here a2) Nk = N/a2 result.Rg = np.sqrt( (a2 * N / 6.) * (1 - 1.5 / Nk + 1.5 / Nk** 2 - 0.75 / Nk ** 3 * (1 - np.exp(-2 * Nk)))) result.volume = V result.contrast = sld result.columnname = 'q; Iq' result.segmentNumber = N/a2 result.mu = math.log(6**0.5*result.Rg/a2, N/a2) result.modelname = inspect.currentframe().f_code.co_name return result
[docs] def alternatingCoPolymer(q, N, na, ba=1, nua=0.5, la=0.154, nb=None, lb=None, bb=None, nub=None): r""" Alternating linear copolymer between collapsed and swollen states. Single chain formfactor assuming alternating blocks of Gaussian coils between collapsed limit and swollen state in good solvent for dilute solutions. The formfactor reproduces diblock (equ 3.6 in [1]_) and symmetric triblock copolymers for N=2,3. Parameters ---------- q : array Scattering vector in units 1/nm. N : int Total number of blocks a,b with N=Na+Nb. For even N: Na=Nb otherwise Na=Nb+1. na,nb : int Number of segments/monomers in a block of type a or b. la,lb : float Segment/monomer length in nm. Default C-C bond length. nua,nub : float Excluded volume parameter *nu* describing chains between collapsed (1/3) and swollen states (3/5). nu =0.5 for a ideal chain or theta solvent condition. ba,bb : float Scattering length respective contrast to solvent of the segment/monomers . Might be calculated as from specific volume and scattering length :math:`ba = V_A (\rho_A-\rho_{solvent})`. For a melt the average scattering length is :math:`\rho = \frac{V_Ab_a + V_Bb_B}{V_A+V_B}` leading to matching condition at low q. For a melt no interaction between chains. Returns ------- dataArray [q, Fq] .I0 forward scattering q=0 Notes ----- We use equ 3.9 to 3.17 in [1]_ with explicit summation over blocks of type A and B with number Na and Nb. .. math:: S(q) &= S_{AA}(q) + S_{BB}(q) + S_{AB}(q) + S_{BA}(q) S_{AA}(q) &= N_a S^{self}_{AA}(q) + S^{inter}_{AA}(q) S^{self}_{AA}(q) &= N_a F^2(\alpha_a,n_a,\nu_a) S^{inter}_{AA}(q) &= 2 n_a \sum_{k=1}^{N_a} (N_a-k) F^2(\alpha_a,n_a,\nu_a)E(\alpha_a,n_a,\nu_a)^{k-1}E(\alpha_a,n_a,\nu_a)^k S_{AB}(q) &= 2 n_a n_b \sum_{k=1}^{N_a-1} (N_a-k) F(\alpha_a,n_a,\nu_a)F(\alpha_b,n_b,\nu_b) E(\alpha_a,n_a,\nu_a)^{k-1}E(\alpha_b,n_b,\nu_b)^{k-1} S_{BA}(q) &= 2 n_a n_b \sum_{k=1}^{N_b} (N_b-k+1) F(\alpha_a,n_a,\nu_a)F(\alpha_b,n_b,\nu_b) E(\alpha_a,n_a,\nu_a)^{k-1}E(\alpha_b,n_b,\nu_b)^{k-1} with scattering variable :math:`\alpha=q^2l^2/6` and .. math:: E(\alpha,n,\nu) &= \sum_{i=1}^n e^{-\alpha(n-1)^{2\nu}} F(\alpha,n,\nu) &= \frac{1}{n} \sum_{i=1}^n e^{-\alpha(i-1)^{2\nu}} \; scattering \; amplitude P(\alpha,n,\nu) &= F^2(\alpha,n,\nu) \; formfactor Notes: - A correction compared to [1]_ for :math:`S_{BA}` is applied to get the correct forward scattering -> (Nb-k+1) - To normalize the formfactor use .I0 . - In the limit :math:`S(q \rightarrow \infty)` the correlation terms should vanish and only the self terms :math:`S^{self}(q)` should remain. Using the equations from [1]_ additional the terms :math:`S_{AB}(q)` and :math:`S_{BA}(q)` with k-1=0 adds. For conventional SAXS/SANS this difference is negligible. Examples -------- Alternating blocks with different contrast and excluded volume parameter. The correlation peak between blocks is only visible for conditions close to matching of A and B segments, which also depends on block length. :: import jscatter as js import numpy as np q=np.r_[0.1:8:0.02] p=js.grace(1,1) for i,nub in enumerate([0.4,0.5,0.6],1): for c,bb in zip([1,2,3,4,6],np.r_[1:-1.1:-0.5]): fq = js.ff.alternatingCoPolymer(q,N=30,na=20,nb=30,bb=bb,nub=nub) p.plot(fq.X,fq.Y,li=[i,2.5,c],sy=0,le=f'bb= {bb}' if i==1 else '') p.yaxis(label='F(q)',scale='l',min=1000,max=1e6,charsize=1.5) p.xaxis(scale='l',label='q / nm\S-1',charsize=1.5) p.legend(x=3,y=5e5) p.text('contrast of B segment',x=2,y=6e5) p.title('alternating block copolymer') p.subtitle(r'N=30; na=20;nb=30; ba=1; for \xn\f{}\sb\N=0.4 (-),0.5(..),0.6(- -)') #p.save(js.examples.imagepath+'/alternatingCoPolymer.jpg') .. image:: ../../examples/images/alternatingCoPolymer.jpg :align: center :width: 50 % :alt: alternatingCoPolymer References ---------- .. [1] SANS from homogeneous polymer mixtures: A unified overview. Hammouda, B. in Polymer Characteristics 87–133 (Springer-Verlag, 1993). doi:10.1007/BFb0025862 """ # equations according to ref [1]_ q=np.r_[0, q] if lb is None: lb = la if nb is None: nb =na if nub is None: nub = nua if bb is None: bb = ba # scattering variables for blocks types a and b aa = q**2*la**2/6 ab = q**2*lb**2/6 def E(a, n, nu): # eq 2.13 return np.exp(-a * (n-1) ** (nu*2)) def F(a, n, nu): # eq 2.14 i = np.c_[1:n+1] return np.exp(-a*(i-1)**(2*nu)).sum(axis=0)/n def P(a, n, nu): # eq. 2.15 i = np.r_[1:n+1] j = np.c_[1:n+1] absij = np.abs(i-j).flatten() return np.exp(-a[:, None] * absij**(2*nu)).sum(axis=-1)/n**2 # even and odd case if N%2 != 0: Na = (N+1)/2 # a's are first and last Nb = (N-1)/2 else: # equal number of A,B segments Na = Nb = N / 2 # to gather all correlations think of a matrix with columns and rows alternating a and b # equ 3.10 self correlations in diagonal Saas = Na * ba**2*na**2 * P(aa, na, nua) Sbbs = Nb * bb**2*nb**2 * P(ab, nb, nub) # correlation same blocks a-a or b-b # equ 3.11 inter block correlations of one type a or b # count of distances (blocks between) k is in (Na-k) ; this is 0 for k=Na so skip it k= np.c_[1:Na] Saal = 2 * ba**2*na**2 * ((Na-k) * F(aa, na, nua)**2 * E(aa, na, nua)**(k-1)*E(ab, nb, nub)**k).sum(axis=0) k= np.c_[1:Nb] Sbbl = 2 * bb**2*nb**2 * ((Nb-k) * F(ab, nb, nub)**2 * E(aa, na, nua)**k*E(ab, nb, nub)**(k-1)).sum(axis=0) # equ 3.9 sum , Na**2 in above Saa = Saas + Saal Sbb = Sbbs + Sbbl # equ 3.13 cross correlations between ab,ba # in matrix between ab , for each a previous b is there except first k= np.c_[1:Na+1] Sab1 = 2*ba*bb*na*nb * \ ((Na-k) * F(aa, na, nua)*F(ab, nb, nub) * E(aa, na, nua)**(k-1)*E(ab, nb, nub)**(k-1)).sum(axis=0) # in matrix between ba for each b a previous a is there k= np.c_[1:Nb+1] Sab2 = 2*ba*bb*na*nb* \ ((Nb-k+1) * F(aa, na, nua)*F(ab, nb, nub) * E(aa, na, nua)**(k-1)*E(ab, nb, nub)**(k-1)).sum(axis=0) S = Saa + Sbb + Sab1 + Sab2 result=dA(np.c_[q[1:], S[1:]].T) result.setColumnIndex(iey=None) result.columnname = 'q; Fq' result.modelname = inspect.currentframe().f_code.co_name result.bb = bb result.ba = ba result.I0 = S[0] return result
[docs] def polymerCorLength(q, xi, m, I0=1): r""" Polymer scattering switching from collapsed over theta solvent to good solvent including chain overlap. Parameters ---------- q : array Wavevectors in units 1/nm xi : float Correlation length m : float Porod exponent describing high q power law. m is related to Flory excluded volume exponent 𝜈 as m=1/𝜈 m=2 is Lorentz function (Ornstein-Zernike critical system) I0 : float scale Returns ------- dataArray [q, Iq] Notes ----- According to [1]_ the polymer scattering in solution can be described by the correlation length xi using: .. math:: I(q) = \frac{I_0}{1+(q\xi)^m} \ with \ m=1/\nu - For collapsed chain 𝜈=1/3 ; m=3 - For theta solvent 𝜈=1/2 ; m=2 - For good solvent 𝜈=3/5 ; m=5/3 For Rg one finds :math:`R_g^2 = \frac{b^2N^{2\nu}}{(2\nu+1)(2\nu+2)}`. For details see [1]_ and [2]_. References ---------- .. [1] Insight Into Chain Dimensions in PEO/Water Solutions B. HAMMOUDA, D. L. Ho, Journal of Polymer Science, Part B: Polymer Physics, 45(16), 2196–2200. https://doi.org/10.1002/polb.21221 .. [2] Insight into Clustering in Poly(ethylene oxide) Solutions B. Hammouda,* D. L. Ho, and S. Kline, Macromolecules 2004, 37, 6932-6937, doi: 10.1021/ma049623d """ result = dA(np.c_[q, I0/(1+(q*xi)**m)].T) result.setColumnIndex(iey=None) result.columnname = 'q; Iq' result.modelname = inspect.currentframe().f_code.co_name result.xi = xi return result
[docs] def ornsteinZernike(q, xi, I0=1): r""" Lorenz function, Ornstein Zernike model of critical systems. The models is also used to describe diffuse scattering. Parameters ---------- q : array Wavevectors in units 1/nm xi : float Correlation length I0 : float scale Returns ------- dataArray [q, Iq] Notes ----- A spatial correlation of the form .. math:: \rho(r)_{OZ}= \frac{\rho_0}{r}e^{-\frac{r}{\xi}} results in the scattering intensity .. math:: I(q) = \frac{I_0}{1+q^2\xi^2} A detailed explanation is found in [2]_. References ---------- .. [1] Accidental deviations of density and opalescence at the critical point of a single substance. Ornstein, L., & Zernike, F. (1914). Proc. Akad. Sci.(Amsterdam), 17(September), 793–806. Retrieved from http://www.dwc.knaw.nl/DL/publications/PU00012727.pdf .. [2] Correlation functions and the critical region of simple fluids. Fisher, M. E. (1964). Journal of Mathematical Physics, 5(7), 944–962. https://doi.org/10.1063/1.1704197 .. [3] Origin of the scattering peak in microemulsions Teubner, M.; Strey, R. Chem. Phys. 1987, 87 (5), 3195–3200 DOI: 10.1063/1.453006 """ result = dA(np.c_[q, I0/(1+q**2*xi**2)].T) result.setColumnIndex(iey=None) result.columnname = 'q; Iq' result.modelname = inspect.currentframe().f_code.co_name result.xi = xi return result
[docs] def DAB(q, xi, I0=1): r""" DAB model for two-phase systems with sharp interface leading to Porod scattering at large q. Debye-Anderson-Brumberger (DAB) model or Debye–Buche function. Parameters ---------- q : array Wavevectors in units 1/nm xi : float Correlation length in units nm. I0 : float scale Returns ------- dataArray [q, Iq] Notes ----- .. math:: I(q) = \frac{I_0}{(1+q^2\xi^2)^2} From [3]_ about gels and inhomogenities and usage of DAB. DAB is used to describe the inhomogenities: "Inhomogeneities in polymer gels are more pronounced after swelling. Regions of greater cross-linking density swell considerably more than regions of lower cross-linking density. The difference grows with increased swelling, and the denser regions of higher cross-linking density can influence the scattering pattern. The static inhomogeneities are not exclusively due to a distribution of cross-links but could be topological in nature or due to the connectivity of the network. This effect was first illustrated by Bastide and Leibler. To account for both the and the spatial distribution of inhomogeneities, the gel structure function has been described as having two contributions, thermal fluctuations from gel strands and the static spatial distribution of inhomogeneities. The phenomenon was later expanded upon by Panyukov and Rabin for poly-electrolyte gels. The simplified version of the structure factor for an inhomogeneous network" With first term as DAB and second as OrnsteinZernike model: .. math:: I(q) = \frac{I_{0,DAB}}{(1+q^2\xi_{DAB}^2)^2} + \frac{I_{0,OZ}}{1+q^2\xi_{OZ}^2} References ---------- .. [1] Scattering by an Inhomogeneous Solid. II. The Correlation Function and Its Application Debye, P., Anderson, R., Brumberger, H.,J. Appl. Phys. 28 (6), 679 (1957). .. [2] Scattering by an Inhomogeneous Solid Debye, P., Bueche, A. M., J. Appl. Phys. 20, 518 (1949) .. [3] Scattering methods for determining structure and dynamics of polymer gels Morozov et al., J. Appl. Phys.129, 071101 (2021);doi: 10.1063/5.003341 """ result = dA(np.c_[q, I0/(1+q**2*xi**2)**2].T) result.setColumnIndex(iey=None) result.columnname = 'q; Iq' result.modelname = inspect.currentframe().f_code.co_name result.xi = xi return result