Source code for jscatter.formel.physics

# -*- 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/>.
#

"""
Physical equations and useful formulas as quadrature of vector functions, parallel execution,
viscosity, compressibility of water, scatteringLengthDensityCalc or sedimentationProfile.
Use scipy.constants for physical constants.

- Each topic is not enough for a single module, so this is a collection.
- All scipy functions can be used. See http://docs.scipy.org/doc/scipy/reference/special.html.
- Statistical functions http://docs.scipy.org/doc/scipy/reference/stats.html.

Mass and scattering length of all elements in Elements are taken from :
 - Mass: http://www.chem.qmul.ac.uk/iupac/AtWt/
 - Neutron scattering length: http://www.ncnr.nist.gov/resources/n-lengths/list.html

Units converted to amu for mass and nm for scattering length.

"""

import inspect
import math
import os
import re
import io
import numbers

import numpy as np
import scipy
import scipy.constants as constants
import scipy.integrate
import scipy.signal
import scipy.optimize
import scipy.special as special

from ..dataarray import dataArray as dA
from ..dataarray import dataList as dL

from ..data import Elements, vdwradii, felectron
from ..data import aquasolventdensity as _aquasolventdensity
from ..data import bufferDensityViscosity as _bufferDensityViscosity

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

__all__ = ['viscosity', 'bufferviscosity', 'waterdensity',
           'scatteringLengthDensityCalc', 'watercompressibility', 'dielectricConstant',
           'cstar', 'Dtrans', 'Drot', 'bicelleRh', 'molarity', 'T1overT2',
           'DrotfromT12', 'sedimentationProfileFaxen', 'sedimentationProfile', 'sedimentationCoefficient',
           'perrinFrictionFactor']

#: Variable to allow printout for debugging as if debug:print('message')
debug = False


[docs] def viscosity(mat='h2o', T=293.15): """ Viscosity of pure solvents. For buffer solvents use bufferviscosity. Parameters ---------- mat : string 'h2o','d2o','toluol','methylcyclohexan', default h2o Solvent T : float Temperature T in Kelvin default 293K Returns ------- float viscosity in Pa*s water H2O ~ 0.001 Pa*s =1 cPoise # Poise=0.1 Pa*s References ---------- .. [1] The Viscosity of Toluene in the Temperature Range 210 to 370 K M. J. Assael, N.K. Dalaouti, J.H., Dymond International Journal of Thermophysics, Vol. 21,291 No. 2, 2000 # accuracy +- 0.4 % laut paper Max error von Experiment data .. [2] Thermal Offset Viscosities of Liquid H2O, D2O, and T2O C. H. Cho, J. Urquidi, S. Singh, and G. Wilse Robinson J. Phys. Chem. B 1999, 103, 1991-1994 """ temp = T if re.match('^' + mat, 'toluol'): # print 'Material Toluol Temperatur', temp , ' Viscosity in mPas (=cP) ', # critical temperature and coefficients Tc, ck0, ck1, ck2, ck3, ck4 = 591.75, 34.054, -219.46, 556.183, -653.601, 292.762 T = temp / Tc vis29315 = 0.0005869 # Pas vis = vis29315 * math.exp(ck0 + ck1 * T + ck2 * T * T + ck3 * T * T * T + ck4 * T * T * T * T) return vis * 1000 elif re.match('^' + mat, 'methylcyclohexan'): # print 'Material Methylcyclohexan Temperatur', temp , ' Viscosity in mPas (=cP)' vis = 0.001 * math.exp(-4.48 + 1217. / temp) return vis * 1000 elif re.match('^' + mat, 'd2o'): # print 'Material D2O Temperatur', temp , ' Viscosity in mPas (=cP) ', T0 = 231.832 # reference Temperature ck0 = 0.0 ck1 = 1.0 ck2 = 2.7990E-3 # Koeffizienten ck3 = -1.6342E-5 ck4 = 2.9067E-8 gamma = 1.55255 dT = temp - T0 vis231832 = 885.60402 # cPK^gamma vis = vis231832 * (ck0 + ck1 * dT + ck2 * dT ** 2 + ck3 * dT ** 3 + ck4 * dT ** 4) ** (-gamma) # print vis return vis * 1e-3 else: # print 'Material H2O Temperatur', temp , ' Viscosity in mPas (=cP) ', T0 = 225.334 # reference Temperature ck0 = 0.0 ck1 = 1.0 ck2 = 3.4741E-3 # Koeffizienten ck3 = -1.7413E-5 ck4 = 2.7719E-8 gamma = 1.53026 dT = temp - T0 vis225334 = 802.25336 # cPK^gamma vis = vis225334 * 1 / ((ck0 + ck1 * dT + ck2 * dT ** 2 + ck3 * dT ** 3 + ck4 * dT ** 4) ** gamma) # print vis return vis * 1e-3
def _convertfromUltrascan(): """ Internal usage to document how bufferComponents.txt was generated Get xml file from ultrascan and convert to ascii file to read on module load (faster than xmltree) We use only the fields we need here. Ultrascan is released under GNU Lesser General Public License, version 3. See notice in bufferComponents.txt """ import xml.etree.ElementTree buffers = xml.etree.ElementTree.parse('bufferComponents.xml').getroot() bl = [] # new bufferlines bl += ['# buffer coefficients for density (dci) and viscosity (vci) as read from Ultrascan 3 ' + '\n'] content = ['name'] + ['dc0', 'dc1', 'dc2', 'dc3', 'dc4', 'dc5'] + ['vc0', 'vc1', 'vc2', 'vc3', 'vc4', 'vc5'] + [ 'unit', 'range'] bl += ['# ' + ' '.join(content) + '\n'] for buff in buffers: name = buff.attrib['name'].title().replace(' ', '').replace('-', '') if name[0].isdigit(): name = name[1:] + name[0] line = [name] line += [buff[0].attrib[attrib] for attrib in ['c0', 'c1', 'c2', 'c3', 'c4', 'c5']] line += [buff[1].attrib[attrib] for attrib in ['c0', 'c1', 'c2', 'c3', 'c4', 'c5']] line += [buff.attrib[attrib].strip().replace(' ', '_') for attrib in ['unit', 'range']] bl += [' '.join(line) + '\n'] bl.sort() with io.open(os.path.join(_path_, 'data', 'bufferComponents.txt'), 'w') as _f: _f.writelines(bl)
[docs] def bufferviscosity(composition, T=293.15, show=False): """ Viscosity of water with inorganic substances as used in biological buffers. Solvent with composition of H2O and D2O and additional components at temperature T. Ternary solutions allowed. Units are mol; 1l h2o = 55.50843 mol Based on data from ULTRASCAN3 [1]_ supplemented by the viscosity of H2O/D2O mixtures for conc=0. Parameters ---------- composition : list of compositional strings Compositional strings of chemical name as 'float'+'name' First float is content in Mol followed by component name as 'h2o' or 'd2o' light and heavy water were mixed with prepended fractions. ['1.5urea','0.1sodiumchloride','2h2o','1d2o'] for 1.5 M urea + 100 mM NaCl in a 2:1 mixture of h2o/d2o. By default '1h2o' is assumed. T : float, default 293.15 Temperature in K show : bool, default False Show composition and validity range of components and result in mPas. Returns ------- float Viscosity in Pa*s Notes ----- - Viscosities of H2O/D2O mixtures mix by linear interpolation between concentrations (accuracy 0.2%) [2]_. - The change in viscosity due to components is added based on data from Ultrascan3 [1]_. - Multicomponent mixtures are composed of binary mixtures. - "Glycerol%" is in unit "%weight/weight" for range="0-32%, here the unit is changed to weight% insthead of M. - Propanol1, Propanol2 are 1-Propanol, 2-Propanol References ---------- .. [1] http://www.ultrascan3.uthscsa.edu/ .. [2] Viscosity of light and heavy water and their mixtures Kestin Imaishi Nott Nieuwoudt Sengers, Physica A: Statistical Mechanics and its Applications 134(1):38-58 .. [3] Thermal Offset Viscosities of Liquid H2O, D2O, and T2O C. H. Cho, J. Urquidi, S. Singh, and G. Wilse Robinson J. Phys. Chem. B 1999, 103, 1991-1994 availible components:: h2o1 d2o1 """ if isinstance(composition, str): composition = [composition] cd2o = 0 ch2o = 0 nwl = {} # nonwaterlist # decompose composition for compo in composition: compo = compo.lower() decomp = re.findall(r'\d+\.\d+|\d+|\D+', compo) if not re.match(r'\d', decomp[0]): raise KeyError('Component %s missing concentration ' % compo) component = ''.join(decomp[1:]) conc = float(decomp[0]) # in Mol if component in ['h2o1', 'h2o']: ch2o += conc elif component in ['d2o1', 'd2o']: cd2o += conc else: nwl[component] = (conc,) + (_bufferDensityViscosity[component][6:14]) if ch2o == 0 and cd2o == 0: # default if no water composition was given ch2o = 1 # # temperature dependent viscosity of h20/d2o mixture as basis in mPas (Ultrascan units for below) ch2od2o = (ch2o + cd2o) ch2o = ch2o / ch2od2o cd2o = cd2o / ch2od2o visc = (ch2o * viscosity(mat='h2o', T=T) + cd2o * viscosity(mat='d2o', T=T)) * 1000. # coefficints all for c=0 give water viscosity (which is not always correct!!) # coefficients[i>0] give increase from conc =0 # so add them up vc = np.r_[0.].repeat(6) # sum coefficients ff = np.r_[1., 1e-3, 1e-2, 1e-3, 1e-4, 1e-6] # standard powers for k, v in nwl.items(): c = v[0] # concentration (converted to mM) coefficients = v[1:7] # coefficients range = v[8] # validity range cp = np.r_[0, c ** 0.5, c, c * c, c ** 3, c ** 4] # concentration powers if show: print('%20s %12.3f M valid: %20s' % (k, c, range)) vc += coefficients * cp if show: print(' h2o %.3f d2o %.3f => visc %.3f mPas' % (ch2o, cd2o, visc)) visc += np.sum(vc * ff) # multiply by standard powers if show: print(' mixture => %.3f mPas' % visc) return visc / 1000. # return use Pa*s
# complete the docstring from above _avlist = sorted(_bufferDensityViscosity.keys()) _i = 0 while _i < len(_avlist): bufferviscosity.__doc__ += ' ' + ''.join([' %-25s' % cc for cc in _avlist[_i:_i + 3]]) + '\n' _i += 3 bufferviscosity.__doc__ += '\n'
[docs] def waterdensity(composition, T=293.15, units='mol', showvalidity=False): """ Density of water with inorganic substances (salts). Solvent with composition of H2O and D2O and additional inorganic components at temperature T. Ternary solutions allowed. Units are mol Parameters ---------- composition : list of compositional strings Compositional string of chemical formula as 'float'+'chemical char' + integer - First float is content in mol (is later normalised to sum of contents) - chemical letter + number of atoms in formula (single atoms append 1 ,fractional numbers allowed) :: 'h2o1' or 'd2o1' light and heavy water with 'd1' for deuterium 'c3h8o3' or 'c3h1d7o3' partial deuterated glycerol ['55.55h2o1','2.5Na1Cl1'] for 2.5 mol NaCl added to 1l h2o (55.55 mol) ['20H2O1','35.55D2O1','0.1Na1Cl1'] h2o/d2o mixture with 100mMol NaCl units : default='mol' Anything except 'mol' unit is mass fraction 'mol' units is mol and mass fraction is calculated as mass=[mol]*mass_of_molecule e.g. 1l Water with 123mM NaCl ['55.5H2O1','0.123Na1Cl1'] T : float, default=293.15 temperature in K showvalidity : bool, default False Show additionally validity range for temperature and concentration according to [4]_. - Temperature range in °C - concentration in wt % or up to a saturated solution (satd) - error in 1/100 % see [4]_. Returns ------- float Density in g/ml Notes ----- - D2O maximum density 1.10596 at T=273.15+11.23 K [1]_ . - For mixtures of H2O/D2O molar volumes add with an accuracy of about 2e-4 cm**3/mol compared to ~18.015 cm**3/mol molar volume [3]_. - Additional densities of binary aqueous solutions [4]_. Water number density :: # 55.5079 mol with 18.015 g/mol js.formel.waterdensity('1H2O1', T=273+4)*1000/18.015 References ---------- .. [1] The dilatation of heavy water K. Stokland, E. Ronaess and L. Tronstad Trans. Faraday Soc., 1939,35, 312-318 DOI: 10.1039/TF9393500312 .. [2] Effects of Isotopic Composition, Temperature, Pressure, and Dissolved Gases on the Density of Liquid Water George S. Kell JPCRD 6(4) pp. 1109-1131 (1977) .. [3] Excess volumes for H2O + D2O liquid mixtures Bottomley G Scott R Australian Journal of Chemistry 1976 vol: 29 (2) pp: 427 .. [4] Densities of binary aqueous solutions of 306 inorganic substances P. Novotny, O. Sohnel J. Chem. Eng. Data, 1988, 33 (1), pp 49–55 DOI: 10.1021/je00051a018 availible components:: h2o1 d2o1 TRIS c4h11n1o3 TABS c8h19n1o6s1 """ mw = 18.01528 # mol weight water T -= 273.15 #: water density def wdensity(T, a0, a1, a2, a3, a4, a5, b): return (a0 + a1 * T + a2 * T ** 2 + a3 * T ** 3 + a4 * T ** 4 + a5 * T ** 5) / (1 + b * T) / 1000. # 5-100 °C # D2O max density 1.10596 at T=11,23°C from Stokeland Trans. Faraday Soc., 1939,35, 312-31 # we use here 1104.633 instead of the original 1104.7056 of Kell to get the max density correct cD2O = [1104.633, 28.88152, -7.652899e-3, -136.61854e-6, 534.7350e-9, -1361.843e-12, 25.91488e-3] # 0-150 K cH2O = [999.84252, 16.945227, -7.9870641e-3, -46.170600e-6, 105.56334e-9, -280.54337e-12, 16.879850e-3] # additional density due to added inorganic components def _getadddensity(c, TT, decompp): pp = _aquasolventdensity[decompp] if decompp == 'c4h11n1o3': return pp[0] * c ** pp[1] elif decompp == 'c8h19n1o6s1': return pp[0] * c ** pp[1] else: if showvalidity: print(decompp, ': Temperaturerange: ', pp[7], ' concentration: ', pp[8], ' error %:', pp[6]) return (pp[0] * c + pp[1] * c * TT + pp[2] * c * TT * TT + pp[3] * c ** (3 / 2.) + pp[4] * c ** ( 3 / 2.) * TT + pp[5] * c ** (3 / 2.) * TT * TT) * 1e-3 cd2o = 0 ch2o = 0 nonwaterlist = {} adddensity = 0 if isinstance(composition, str): composition = [composition] for compo in composition: compo = compo.lower() decomp = re.findall(r'\d+\.\d+|\d+|\D+', compo) if not re.match(r'\d', decomp[0]): # add a 1 as concentration in front if not there decomp = [1] + decomp if not re.match(r'\d+\.\d+|\d+', decomp[-1]): raise KeyError('last %s Element missing following number ' % decomp[-1]) mass = np.sum([Elements[ele][1] * float(num) for ele, num in zip(decomp[1:][::2], decomp[1:][1::2])]) if units.lower() != 'mol': # we convert here from mass to mol concentration = float(decomp[0]) / mass else: concentration = float(decomp[0]) # concentration of this component decomp1 = ''.join(decomp[1:]) if decomp1 == 'h2o1': ch2o += concentration elif decomp1 == 'd2o1': cd2o += concentration else: nonwaterlist[decomp1] = concentration wff = (1000 / mw) / (ch2o + cd2o) for k, v in nonwaterlist.items(): # additional density due to components adddensity += _getadddensity(v * wff, T, k) density = cd2o / (cd2o + ch2o) * wdensity(T, cD2O[0], cD2O[1], cD2O[2], cD2O[3], cD2O[4], cD2O[5], cD2O[6]) density += ch2o / (cd2o + ch2o) * wdensity(T, cH2O[0], cH2O[1], cH2O[2], cH2O[3], cH2O[4], cH2O[5], cH2O[6]) return density + adddensity
# complete the docstring from above _aqlist = sorted(_aquasolventdensity.keys()) _i = 0 while _i < len(_aqlist): waterdensity.__doc__ += ' ' + ''.join([' %-12s' % cc for cc in _aqlist[_i:_i + 6]]) + '\n' _i += 6 waterdensity.__doc__ += '\n'
[docs] def scatteringLengthDensityCalc(composition, density=None, T=293, units='mol', mode='all'): """ Scattering length density of composites and water with inorganic components for xrays and neutrons. Parameters ---------- composition : list of concentration + chemical formula A string with chemical formula as letter + number and prepended concentration in mol or mmol. E.g. '0.1C3H8O3' or '0.1C3H1D7O3' for glycerol and deuterated glycerol ('D' for deuterium). - For single atoms append 1 to avoid confusion. Fractional numbers allowed, but think of meaning (Isotope mass fraction??) - For compositions use a list of strings preceded by mass fraction or concentration in mol of component. This will be normalized to total amount Examples: - ['4.5H2O1','0.5D2O1'] mixture of 10% heavy and 90% light water. - ['1.0h2o','0.1c3h8o3'] for 10% mass glycerol added to 100% h2o with units='mass' - ['55000H2O1','50Na3P1O4','137Na1Cl1'] for a 137mMol NaCl +50mMol phophate H2O buffer (1l is 55000 mM H2O) - ['1Au1'] gold with density 19.302 g/ml Remember to adjust density. density : float, default=None Density in g/cm**3 = g/ml. - If not given function waterdensity is tried to calculate the solution density with inorganic components. In this case 'h2o1' and/or 'd2o1' need to be in composition. - Density can be measure by weighting a volume from pipette (lower accuracy) or densiometry (higher accuracy). - Estimate for deuterated compounds from protonated density according to additional D. Mass change is given with mode='all'. units : 'mol' Anything except 'mol' prepended unit is mass fraction (default). 'mol' prepended units is mol and mass fraction is calculated as mass=[mol]*mass_of_molecule e.g. 1l Water with 123mmol NaCl ['55.5H2O1','0.123Na1Cl1'] mode : - 'xsld' xray scattering length density in nm**-2 - 'edensity' electron density in e/nm**3 - 'ncohsld' coherent scattering length density in nm**-2 - 'incsld' incoherent scattering length density in nm**-2 - 'num' number density of components in 1/nm**3 - 'all' [xsld, edensity, ncohsld, incsld, masses, masses full protonated, masses full deuterated, d2o/h2o mass fraction, d2o/h2o mol fraction] = 'all2' [xsld, edensity, ncohsld, incsld, masses, masses full protonated, masses full deuterated, d2o/h2o number fraction, number densities in 1/nm³, density in g/cm³] T : float, default=293 Temperature in °K Returns ------- float, list sld corresponding to mode Notes ----- - edensity=be*massdensity/weightpermol*sum_atoms(numberofatomi*chargeofatomi) - be = scattering length electron =µ0*e**2/4/pi/m=2.8179403267e-6 nm - masses, masses full protonated, masses full deuterated for each chemical in composition. - In mode 'all' the masses can be used to calc the deuterated density if same volume is assumed. e.g. fulldeuterated_density=protonated_density/massfullprotonated*massfulldeuterated For density reference of H2O/D2O see waterdensity. Examples -------- :: # 5% D2O in H2O with 10% mass NaCl js.formel.scatteringLengthDensityCalc(['9.5H2O1','0.5D2O1','1Na1Cl1'],units='mass') # protein NaPi buffer in D2O prevalue in mmol; 55507 mmol H2O is 1 liter. # because of the different density the sum of number density is not 55.507 mol but 55.191 mol. js.formel.scatteringLengthDensityCalc(['55507D2O1','50Na3P1O4','137Na1Cl1']) # silica js.formel.scatteringLengthDensityCalc('1Si1O2',density=2.65) # gold js.formel.scatteringLengthDensityCalc(['1Au1'],density=19.32) # PEG1000 js.formel.scatteringLengthDensityCalc(['1C44H88O23'],density=1.21) """ edensity = [] bcdensity = [] bincdensity = [] ndensity = [] total = 0 # totalmass = 0 md2o = 0 # mass mh2o = 0 nd2o = 0 # number nh2o = 0 massfullprotonated = [] massfulldeuterated = [] totalmass = [] if not isinstance(density, (numbers.Number, np.ndarray)): density = waterdensity(composition, T=T, units=units) density = float(density) if isinstance(composition, str): composition = [composition] for compo in composition: # decompose in numbers as fractions and characters determining element compo = compo.lower() decomp = re.findall(r'\d+\.\d+|\d+|\D+', compo) # add 1 if no concentration in front if not re.match(r'\d', decomp[0]): decomp = [1] + decomp mass = np.sum([Elements[ele][1] * float(num) for ele, num in zip(decomp[1:][::2], decomp[1:][1::2])]) # if units=mol we convert here from mol to mass fraction and opposite if units.lower() == 'mol': numberfraction = float(decomp[0]) massfraction = numberfraction * mass else: massfraction = float(decomp[0]) numberfraction = massfraction / mass # check for completeness of last given element at end if not re.match(r'\d+\.\d+|\d+', decomp[-1]): raise KeyError('last %s Element missing following number ' % decomp[-1]) sumZ = 0 # sum electrons b_coherent = 0 # coherent neutron scattering length b_incoherent = 0 # incoherent neutron scattering length massfullprotonated += [0] massfulldeuterated += [0] # fill above with content according to composition for ele, num in zip(decomp[1:][::2], decomp[1:][1::2]): if ele in Elements.keys(): num = float(num) sumZ += Elements[ele][0] * num massfullprotonated[-1] += (Elements['h'][1] * num) if ele in ['h', 'd'] else (Elements[ele][1] * num) massfulldeuterated[-1] += (Elements['d'][1] * num) if ele in ['h', 'd'] else (Elements[ele][1] * num) b_coherent += Elements[ele][2] * num b_incoherent += Elements[ele][3] * num else: print('decomposed to \n', decomp) raise KeyError('"%s" not found in Elements' % ele) # density[g/cm^3] / mass[g/mol]= N in mol/cm^3 --> N*Z is charge density if ''.join(decomp[1:]) == 'h2o1': mh2o += massfraction nh2o += numberfraction if ''.join(decomp[1:]) == 'd2o1': md2o += massfraction nd2o += numberfraction edensity.append(massfraction * density * (constants.N_A / 1e21) / mass * sumZ) bcdensity.append(massfraction * density * (constants.N_A / 1e21) / mass * b_coherent) bincdensity.append(massfraction * density * (constants.N_A / 1e21) / mass * b_incoherent) ndensity.append(massfraction * density * (constants.N_A / 1e21) / mass) totalmass += [mass] total += massfraction # return valuse asked for if mode[0] == 'e': return sum(edensity) / total elif mode[:3] == 'num': return np.array(ndensity) / total elif mode[0] == 'x': return sum(edensity) / total * felectron elif mode[0] == 'n': return sum(bcdensity) / total elif mode[0] == 'i': return sum(bincdensity) / total elif mode == 'all2': return sum(edensity) / total * felectron, \ sum(edensity) / total, \ sum(bcdensity) / total, \ sum(bincdensity) / total, \ totalmass, \ massfullprotonated, \ massfulldeuterated, \ nd2o / (nh2o + nd2o) if nh2o + nd2o != 0 else 0, \ np.array(ndensity) / total, \ density else: return sum(edensity) / total * felectron, \ sum(edensity) / total, \ sum(bcdensity) / total, \ sum(bincdensity) / total, \ totalmass, \ massfullprotonated, \ massfulldeuterated, \ md2o / (mh2o + md2o) if mh2o + md2o != 0 else 0, \ nd2o / (nh2o + nd2o) if nh2o + nd2o != 0 else 0
[docs] def watercompressibility(d2ofract=1, T=278, units='psnmg'): r""" Isothermal compressibility of H2O and D2O mixtures. Compressibility :math:`\kappa` in units ps²nm/g or in 1/bar. Linear mixture according to d2ofract. Parameters ---------- d2ofract : float, default 1 Fraction D2O T : float, default 278K Temperature in K units : string 'psnmg' ps^2*nm/(g/mol) or 1/bar Returns ------- float Notes ----- For the structure factor e.g. of water one finds in agreement to literature .. math:: S(0) = kT n \kappa = 0.064 with thermal energy kT and number density n, - :math:`n = 55.5mol/l = 33.42 /nm³`, - :math:`kT=300K*1.380649e^{-23} J/K = 414e^{-23} kgm²/s² = 414e^{-26} g nm²/ps²` - :math:`\kappa(300K)=4.625e20 g/nm/ps²` Examples -------- :: import jscatter as js n = js.formel.waterdensity('h2o1',T=300) * 1000/18 *6.023e23/1e24 ka = js.formel.watercompressibility(T=300) kT = 300*1.380649e-26 S0 = kT*n*ka # => 0.06388 References ---------- .. [1] Isothermal compressibility of Deuterium Oxide at various Temperatures Millero FJ and Lepple FK Journal of chemical physics 54,946-949 (1971) http://dx.doi.org/10.1063/1.1675024 .. [2] Precise representation of volume properties of water at one atmosphere G. S. Kell J. Chem. Eng. Data, 1967, 12 (1), pp 66–69 http://dx.doi.org/10.1021/je60032a018 """ t = T - 273.15 def h2o(t): ll = (50.9804 - 0.374957 * t + 7.21324e-3 * t ** 2 - 64.1785e-6 * t ** 3 + 0.343024e-6 * t ** 4 - 0.684212e-9 * t ** 5) return 1e-6 * ll def d2o(t): return 1e-6 * (53.61 - 0.4717 * t + 0.009703 * t ** 2 - 0.0001015 * t ** 3 + 0.0000005299 * t ** 4) comp_1overbar = d2ofract * d2o(t) + (1 - d2ofract) * h2o(t) # units ps, nm, g if units == 'psnmg': # bar = 1e5 Pa =1e5 *1000*g/m/s² = 1e5 *1000*g/(1e9nm * 1e24 ps²) = 1e25 g/nm/ps² # factor=1e-8*m*s**2/(g/Nav) factor = 1e-8 * 1e9 * 1e12 ** 2 else: factor = 1 compressibility_psnmgUnits = comp_1overbar * factor return compressibility_psnmgUnits
[docs] def dielectricConstant(material='d2o', T=293.15, conc=0, delta=5.5): r""" Dielectric constant of H2O and D2O buffer solutions. Dielectric constant :math:`\epsilon` of H2O and D2O (error +- 0.02) with added buffer salts. .. math:: \epsilon (c)=\epsilon (c=0)+2c\: delta\; for\; c<2M Parameters ---------- material : string 'd2o' (default) or 'h2o' Material 'd2o' (default) or 'h2o' T : float Temperature in °C conc : float Salt concentration in mol/l. delta : float Total excess polarisation dependent on the salt and presumably on the temperature! Returns ------- float Dielectric constant Notes ----- ====== ========== =========================== Salt delta(+-1) deltalambda (not used here) ====== ========== =========================== HCl -10 0 LiCl 7 -3.5 NaCl 5.5 -4 default KCl 5 -4 RbCl 5 -4.5 NaF 6 -4 KF 6.5 -3.5 NaI -7.5 -9.5 KI -8 -9.5 MgCI, -15 -6 BaCl2 -14 -8.5 LaCI. -22 -13.5 NaOH -10.5 -3 Na2SO. -11 -9.5 ====== ========== =========================== References ---------- .. [1] Dielectric Constant of Water from 0 to 100 C. G . Malmberg and A. A. Maryott Journal of Research of the National Bureau of Standards, 56,1 ,369131-56--1 (1956) Research Paper 2641 .. [2] Dielectric Constant of Deuterium Oxide C.G Malmberg, Journal of Research of National Bureau of Standards, Vol 60 No 6, (1958) 2874 http://nvlpubs.nist.gov/nistpubs/jres/60/jresv60n6p609_A1b.pdf .. [3] Dielectric Properties of Aqueous Ionic Solutions. Parts I and II Hasted et al. J Chem Phys 16 (1948) 1 http://link.aip.org/link/doi/10.1063/1.1746645 """ if material == 'h2o': diCo = lambda t: 87.740 - 0.4008 * (t - 273.15) + 9.398e-4 * (t - 273.15) ** 2 - 1.410e-6 * (t - 273.15) ** 3 return diCo(T) + 2 * delta * conc elif material == 'd2o': diCo = lambda t: 87.48 - 0.40509 * (t - 273.15) + 9.638e-4 * (t - 273.15) ** 2 - 1.333e-6 * (t - 273.15) ** 3 return diCo(T) + 2 * delta * conc
###################################################
[docs] def cstar(Rg, Mw): r""" Overlap concentration :math:`c^*` for a polymer. Equation 3 in [1]_ (Cotton) defines :math:`c^*` as overlap concentration of space filling volumes corresponding to a cube or sphere with edge/radius equal to :math:`R_g` .. math:: \frac{ M_w }{ N_A R_g^3} \approx c^* \approx \frac{3M_w}{4N_A \pi R_g^3} while equ. 4 uses cubes with :math:`2R_g` (Graessley) :math:`c^* = \frac{ M_w }{ N_A 2R_g^3 }` . Parameters ---------- Rg : float in nm radius of gyration Mw : float molecular weight Returns ------- float : x3 Concentration limits [cube_rg, sphere_rg, cube_2rg] in units g/l. References ---------- .. [1] Overlap concentration of macromolecules in solution Ying, Q. & Chu, B. Macromolecules 20, 362–366 (1987) """ cstar_sphere = 3. * Mw / (constants.Avogadro * 4 * np.pi * (Rg * 1E-9) ** 3) / 1000 # in g/l cstar_cube = Mw / (constants.Avogadro * (Rg * 1E-9) ** 3) / 1000 # in g/l cstar_cube2 = Mw / (constants.Avogadro * (2 * Rg * 1E-9) ** 3) / 1000 # in g/l return cstar_cube, cstar_sphere, cstar_cube2
[docs] def Dtrans(Rh, Temp=293.15, solvent='h2o', visc=None): r""" Translational diffusion of a sphere. .. math:: D = \frac{k_\text{B} T}{6 \pi \eta R_h} Parameters ---------- Rh : float Hydrodynamic radius in nm. Temp : float Temperature T in K. solvent : float Solvent type as in viscosity; used if visc==None. visc : float Viscosity :math:`\eta` in Pas => H2O ~ 0.001 Pas =1 cPoise. If visc=None the solvent viscosity is calculated from function viscosity(solvent ,temp) with solvent e.g.'h2o' (see viscosity). Returns ------- D : float Translational diffusion coefficient : float in nm^2/ns. """ if visc is None: visc = viscosity(solvent, Temp) # unit Pa*s= kg/m/s D0 = constants.k * Temp / (6 * math.pi * visc * Rh * 1e-9) # Rh in m D0 in m**2/s return D0 * 1e9 # with conversion to unit nm**2/ns
D0 = Dtrans
[docs] def Drot(Rh, Temp=293.15, solvent='h2o', visc=None): r""" Rotational diffusion of a sphere. .. math:: D = \frac{k_\text{B} T}{8 \pi \eta R_h^3} Parameters ---------- Rh : float Hydrodynamic radius in nm. Temp : float Temperature in K. solvent : float Solvent type as in viscosity; used if visc==None. visc : float Viscosity in Pas => H2O ~ 0.001 Pa*s =1 cPoise. If visc=None the solvent viscosity is calculated from function viscosity(solvent ,temp) with solvent e.g.'h2o'. Returns ------- float Rotational diffusion coefficient in 1/ns. """ if visc is None: visc = viscosity(solvent, Temp) # conversion from Pa*s= kg/m/s Dr = constants.k * Temp / (8 * math.pi * visc * (Rh * 1e-9) ** 3) # Rh in m return Dr * 1e-9 # 1/ns
[docs] def bicelleRh(Q, rim, thickness, k=1/0.6): r""" Hydrodynamic radius Rh of an ideal bicelle corrected for head group area. Parameters ---------- Q : float Ratio of lipid composition rim : float Radius of the rim. thickness : float Thickness of the bicelle planar region k : float Head group area ratio. like 1/0.6 for rim DHCP 1nm² and planar DMPC 0.6nm² Returns ------- [Rh : float, R: float, R + rim] R radius of the planar region Notes ----- Bicelle radius R with lipid area correction factor k [1]_ : .. math:: R = \frac{1}{2} k r Q [\pi +(\pi^2 + 8k/Q)^{1/2}] Rh of a (rectangular) disk with radius :math:`r^{\prime}` and thickness *t* (same for a longer rod t>r') [2]_ : .. math:: R_h = \frac{3}{2}r^{\prime} \Big[[1+(\frac{t}{2r^{\prime}})^2]^{1/2}-\frac{t}{2r^{\prime}} + \frac{2r^{\prime}}{t} ln\big(\frac{t}{2r^{\prime}} + [1+(\frac{t}{2r^{\prime}})^2]^{1/2}\big)\Big]^{-1} with :math:`r^{\prime} = R+r` outer radius, rim radius :math:`r`, lipid ratio :math:`Q`, thickness *t* It should be noted that in the references reporting this equation the hydrodynamic radius from DLS measurements is reported to be concentration dependent. This results from ignoring the structure factor effects (see :py:func:`~jscatter.structurefactor.hydrodynamicFunct`) and leads to misinterpretation of the disc radius. References ---------- .. [1] Structural Evaluation of Phospholipid Bicelles for Solution-State Studies of Membrane-Associated Biomolecules Glover, et al.Biophysical Journal 81(4), 2163–2171 (2001) .. [2] Quasielastic Light-Scattering Studies of Aqueous Biliary Lipid Systems. Mixed Micelle Formation in Bile Salt-Lecithin Solutions Mazer et al.Biochemistry 19, 601–615 (1980), https://doi.org/10.1021/bi00545a001 """ R = 0.5*k*rim*Q*(np.pi+(np.pi**2+8*k/Q)**0.5) rr = R + rim to2r = thickness/2/rr tor1 = (1 + to2r**2)**0.5 Rh = 1.5*rr/(-to2r + tor1 + np.log(to2r + tor1) / to2r) return Rh, R, rr
[docs] def molarity(objekt, c, total=None): """ Calculates the molarity. Parameters ---------- objekt : object,float Objekt with method .mass() or molecular weight in Da. c : float Concentration in g/ml -> mass/Volume total : float, default None Total volume in milliliter [ml] Concentration is calculated by c[g]/total[ml] if given. Returns ------- float molarity in mol/liter (= mol/1000cm^3) """ if c > 1: print('c limited to 1') c = 1. if hasattr(objekt, 'mass'): mass = objekt.mass() else: mass = objekt if total is not None: c = abs(float(c) / (float(total))) # pro ml (cm^^3) water density =1000g/liter if c > 1: print('concentration c has to be smaller 1 unit is g/ml') return weightPerl = c * 1000 # weight per liter numberPerl = (weightPerl / (mass / constants.N_A)) molarity = numberPerl / constants.N_A return molarity
[docs] def T1overT2(tr=None, Drot=None, F0=20e6, T1=None, T2=None): r""" Calculates the T1/T2 from a given rotational correlation time tr or Drot for proton relaxation measurement. tr=1/(6*D_rot) with rotational diffusion D_rot and correlation time tr. Parameters ---------- tr : float Rotational correlation time. Drot : float If given tr is calculated from Drot. F0 : float NMR frequency e.g. F0=20e6 Hz=> w0=F0*2*np.pi is for Bruker Minispec with B0=0.47 Tesla T1 : float NMR T1 result in s T2 : float NMR T2 resilt in s to calc t12 directly Returns ------- float T1/T2 Notes ----- :math:`J(\omega)=\tau/(1+\omega^2\tau^2)` :math:`T1^{-1}=\frac{\sigma}{3} (2J(\omega_0)+8J(2\omega_0))` :math:`T2^{-1}=\frac{\sigma}{3} (3J(0)+ 5J(\omega_0)+2J(2\omega_0))` :math:`tr=T1/T2` References ---------- .. [1] Intermolecular electrostatic interactions and Brownian tumbling in protein solutions. Krushelnitsky A Physical chemistry chemical physics 8, 2117-28 (2006) .. [2] The principle of nuclear magnetism A. Abragam Claredon Press, Oxford,1961 """ w0 = F0 * 2 * np.pi J = lambda w, tr: tr / (1 + w ** 2 * tr ** 2) if Drot is not None: tr = 1. / (6 * Drot) t1sig3 = 1. / (2. * J(w0, tr) + 8. * J(2 * w0, tr)) t2sig3 = 1. / (3. * tr + 5 * J(w0, tr) + J(2 * w0, tr)) if T1 is not None: print('T1: %(T1).3g sigma = %(sigma).4g' % {'T1': T1, 'sigma': t1sig3 * 3. / T1}) if T2 is not None: print('T2: %(T2).3g sigma = %(sigma).4g' % {'T2': T2, 'sigma': t2sig3 * 3. / T2}) return t1sig3 / t2sig3
[docs] def DrotfromT12(t12=None, Drot=None, F0=20e6, Tm=None, Ts=None, T1=None, T2=None): """ Rotational correlation time from T1/T2 or T1 and T2 from NMR proton relaxation measurement. Allows to rescale by temperature and viscosity. Parameters ---------- t12 : float T1/T2 from NMR with unit seconds Drot : float !=None means output Drot instead of rotational correlation time. F0 : float Resonance frequency of NMR instrument. For Hydrogen F0=20 MHz => w0=F0*2*np.pi Tm: float Temperature of measurement in K. Ts : float Temperature needed for Drot -> rescaled by visc(T)/T. T1 : float NMR T1 result in s T2 : float NMR T2 result in s to calc t12 directly remeber if the sequence has a factor of 2 Returns ------- float Correlation time or Drot Notes ----- See T1overT2 """ if T1 is not None and T2 is not None and t12 is None: t12 = T1 / T2 if Tm is None: Tm = 293 if Ts is None: Ts = Tm if t12 is not None: diff = lambda tr, F0: T1overT2(tr=tr, Drot=None, F0=F0, T1=None, T2=None) - t12 # find tr where diff is zero to invert the equation trr = scipy.optimize.brentq(diff, 1e-10, 1e-5, args=(F0,)) # rescale with visc(T)/T tr = trr * (Tm / viscosity('d2o', T=Tm)) / (Ts / viscosity('d2o', T=Ts)) print('tau_rot: {trr:.3g} at Tm={Tm:.5g} \ntau_rot: {tr:.5g} at Ts={Ts:.3g} \n ' '(scalled by Tm/viscosity(Tm)/(T/viscosity(T)) = {rv:.4g}'. format(trr=trr, Tm=Tm, tr=tr, Ts=Ts, rv=tr / trr)) else: raise Exception('give t12 or T1 and T2') # temp = T1overT2(trr, F0=F0, T1=T1, T2=T2) print('D_rot= : %(drot).4g ' % {'drot': 1 / (6 * tr)}) if Drot is not None: Drot = 1 / (6 * tr) print('returns Drot') return Drot return tr
[docs] def sedimentationProfileFaxen(t=1e3, rm=48, rb=85, number=100, rlist=None, c0=0.01, s=None, Dt=1.99e-11, w=246, Rh=10, visc='h2o', temp=293, densitydif=None): """ Faxen solution to the Lamm equation of sedimenting particles in centrifuge; no bottom part. Bottom equillibrium distribution is not in Faxen solution included. Results in particle distribution along axis for time t. Parameters ---------- t : float Time after start in seconds. If list, results at these times is given as dataList. rm : float Axial position of meniscus in mm. rb : float Axial position of bottom in mm. rlist : array, optional Explicit list of radial values to use between rm=max(rlist) and rb=min(rlist) number : integer Number of points between rm and rb to calculate. c0 : float Initial concentration in cell; just a scaling factor. s : float Sedimentation coefficient in Svedberg; 77 S is r=10 nm particle in H2O. Dt : float Translational diffusion coefficient in m**2/s; 1.99e-11 is r=10 nm particle. w : float Radial velocity rounds per second; 246 rps=2545 rad/s is 20800g in centrifuge fresco 21. Rh : float Hydrodynamic radius in nm ; if given Dt and s are calculated from Rh. visc : float, 'h2o','d2o' Viscosity in units Pas. If 'h2o' or 'd2o' the corresponding viscosity at given temperature is used. densitydif : float Density difference between solvent and particle in g/ml. Protein in 'h2o'=> is used =>1.37-1.= 0.37 g/cm**3 temp : float temperature in K. Returns ------- dataArray, dataList Concentration distribution : dataArray, dataList .pelletfraction is the content in pellet as fraction already diffused out .rmeniscus Notes ----- Default values are for Heraeus Fresco 21 at 21000g. References ---------- .. [1] Über eine Differentialgleichung aus der physikalischen Chemie. Faxén, H. Ark. Mat. Astr. Fys. 21B:1-6 (1929) """ # get solvent viscosity if visc in ['h2o', 'd2o']: visc = viscosity(visc, temp) if densitydif is None: densitydif = 0.37 # protein - water densitydif *= 1e3 # to kg/m³ svedberg = 1e-13 if Rh is not None: Dt = constants.k * temp / (6 * math.pi * visc * Rh * 1e-9) s = 2. / 9. / visc * densitydif * (Rh * 1e-9) ** 2 else: s *= svedberg # to SI units rm = rm /1000. rb = rb /1000. # end r = np.r_[rm:rb:number * 1j] # nn points if rlist is not None: rm = min(rlist) # rb = max(rlist) # not used here r = rlist / 1000. w = w * 2 * np.pi timelist = dL() for tt in np.atleast_1d(t): ct = (0.5 * c0 * np.exp(-2. * s * w ** 2 * tt)) cr = (1 - scipy.special.erf((rm * (w ** 2 * s * tt + np.log(rm) - np.log(r))) / (2. * np.sqrt(Dt * tt)))) timelist.append(dA(np.c_[r * 1000, cr * ct].T)) timelist[-1].time = tt timelist[-1].rmeniscus = rm timelist[-1].w = w timelist[-1].Dt = Dt timelist[-1].c0 = c0 timelist[-1].viscosity = visc timelist[-1].sedimentation = s / svedberg timelist[-1].pelletfraction = 1 - scipy.integrate.simps(y=timelist[-1].Y, x=timelist[-1].X) / ( max(r) * 1000 * c0) timelist[-1].modelname = inspect.currentframe().f_code.co_name if Rh is not None: timelist[-1].Rh = Rh if len(timelist) == 1: return timelist[0] return timelist
[docs] def sedimentationProfile(t=1e3, rm=48, rb=85, number=100, rlist=None, c0=0.01, S=None, Dt=None, omega=246, Rh=10, temp=293, densitydif=0.37, visc='h2o'): r""" Concentration profile of sedimenting particles in a centrifuge including bottom equilibrium distribution. Approximate solution to the Lamm equation including the bottom equilibrium distribution which is not included in the Faxen solution. This calculates equ. 28 in [1]_. Results in particle concentration profile between rm and rb for time t with a equal distribution at the start. Parameters ---------- t : float or list Time after centrifugation start in seconds. If list, a dataList for all times is returned. rm : float Axial position of meniscus in mm. rb : float Axial position of bottom in mm. number : int Number of points between rm and rb to calculate. rlist : list of float Explicit list of positions where to calculate e.g.to zoom bottom. c0 : float Initial concentration in cell; just a scaling factor. S : float Sedimentation coefficient in units Svedberg; 82 S is r=10 nm protein in H2O at T=20C. Dt : float Translational diffusion coefficient in m**2/s; 1.99e-11 is r=10 nm particle. omega : float Radial velocity rounds per second; 14760rpm = **246 rps** = 1545 rad/s is 20800g in centrifuge fresco 21. Rh : float Hydrodynamic radius in nm ; if given the Dt and s are calculated from this. densitydif : float Density difference between solvent and particle in g/ml; Protein in 'h2o' => 1.37-1.= 0.37 g/cm**3. visc : float, 'h2o', 'd2o' Viscosity of the solvent in Pas. (H2O ~ 0.001 Pa*s =1 cPoise) If 'h2o' or 'd2o' the corresponding viscosity at given temperature is used. temp : float temperature in K. Returns ------- dataArray, dataList Concentration profile Columns [position in [mm]; conc ; conc_meniscus_part; conc_bottom_part] Notes ----- From [1]_:"The deviations from the expected results are smaller than 1% for simulated curves and are valid for a great range of molecular masses from 0.4 to at least 7000 kDa. The presented approximate solution, an essential part of LAMM allows the estimation of s and D with an accuracy comparable to that achieved using numerical solutions, e.g the program SEDFIT of Schuck et al." Default values are for Heraeus Fresco 21 at 21000g. Examples -------- Cleaning from aggregates by sedimantation. Sedimentation of protein (R=2 nm) with aggregates of 100nm size. :: import numpy as np import jscatter as js t1=np.r_[60:1.15e3:11j] # time in seconds # open plot() p=js.grace(1.5,1.5) p.multi(2,1) # calculate sedimentation profile for two different particles # data correspond to Fresco 21 with dual rotor # default is solvent='h2o',temp=293 g=21000. # g # RZB number omega=g*246/21000 D2nm=js.formel.sedimentationProfile(t=t1,Rh=2,densitydif=0.37, number=1000) D50nm=js.formel.sedimentationProfile(t=t1,Rh=50,densitydif=0.37, number=1000) # plot it p[0].plot(D2nm,li=[1,2,-1],sy=0,legend='t=$time s' ) p[1].plot(D50nm,li=[1,2,-1],sy=0,legend='t=$time s' ) # pretty up p[0].yaxis(min=0,max=0.05,label='concentration') p[1].yaxis(min=0,max=0.05,label='concentration') p[1].xaxis(label='position mm') p[0].xaxis(label='') p[0].text(x=70,y=0.04,string=r'R=2 nm \nno sedimantation') p[1].text(x=70,y=0.04,string=r'R=50 nm \nfast sedimentation') p[0].legend(x=42,y=0.05,charsize=0.5) p[1].legend(x=42,y=0.05,charsize=0.5) p[0].title('Concentration profile in first {0:} s'.format(np.max(t1))) p[0].subtitle('2nm and 50 nm particles at 21000g ') #p.save(js.examples.imagepath+'/sedimentation.jpg') .. image:: ../../examples/images/sedimentation.jpg :align: center :height: 300px :alt: convolve Sedimentation (up concentration) of unilamellar liposomes of DOPC. The density of DOPC is about 1.01 g/ccm in water with 1 g/ccm. Lipid volume fraction is 33% for 50nm radius (10% for 200 nm) for a bilayer thickness of 6.5 nm. :: import numpy as np import jscatter as js t1=np.r_[100:6e3:11j] # time in seconds # open plot() p=js.grace(1.5,1.5) p.multi(2,1) # calculate sedimentation profile for two different particles # data correspond to Fresco 21 with dual rotor # default is solvent='h2o',temp=293 g=21000. # g # RZB number omega=g*246/21000 D100nm=js.formel.sedimentationProfile(t=t1,Rh=50,c0=0.05,omega=omega,rm=48,rb=85,densitydif=0.003) D400nm=js.formel.sedimentationProfile(t=t1,Rh=200,c0=0.05,omega=omega,rm=48,rb=85,densitydif=0.001) # plot it p[0].plot(D100nm,li=[1,2,-1],sy=0,legend='t=$time s' ) p[1].plot(D400nm,li=[1,2,-1],sy=0,legend='t=$time s' ) # pretty up p[0].yaxis(min=0,max=0.2,label='concentration') p[1].yaxis(min=0,max=0.2,label='concentration') p[1].xaxis(label='position mm') p[0].xaxis(label='') p[0].text(x=70,y=0.15,string='D=100 nm') p[1].text(x=70,y=0.15,string='D=400 nm') p[0].legend(x=42,y=0.2,charsize=0.5) p[1].legend(x=42,y=0.2,charsize=0.5) p[0].title('Concentration profile in first {0:} s'.format(np.max(t1))) p[0].subtitle('at 21000g ') References ---------- .. [1] A new approximate whole boundary solution of the Lamm equation for the analysis of sedimentation velocity experiments J. Behlke, O. Ristau Biophysical Chemistry 95 (2002) 59–68 """ # do all in SI units svedberg = 1e-13 # s if visc in ['h2o', 'd2o']: visc = viscosity(visc, temp) # in Pa*s= kg/m/s densitydif *= 1e3 # g/ccm to kg/m³ if isinstance(t, numbers.Number): t = np.r_[t] if Rh is not None: Dt = constants.k * temp / (6 * math.pi * visc * Rh * 1e-9) S = 2. * densitydif * (Rh * 1e-9) ** 2 / (9. * visc) else: S *= svedberg rm = rm /1000. # meniscus in m rb = rb /1000. # bottom in m r = np.r_[rm:rb:number * 1j] # nn points in m if rlist is not None: # explicit given list between meniscus and bottom r = rlist / 1000. # create variables for calculation omega = omega * 2 * np.pi # in rad taulist = 2 * S * omega ** 2 * np.atleast_1d(t) # timevariable for moving boundary # define functions using scipy and numpy functions erfc = scipy.special.erfc # complementary error function erfcx = scipy.special.erfcx # scaled complementary error function exp = np.exp sqrt = np.sqrt # moving meniscus part eps = 2 * Dt / (S * omega ** 2 * rm ** 2) w = 2 * (r / rm - 1) b = 1 - eps / 2. nn = 200 def c1(tau): # moving boundary return erfc( (exp(tau / 2.) - 0.5 * w - 1 + 0.25 * eps * (exp(-tau / 2.) - exp(tau / 2.))) / sqrt(eps * (exp(tau) - 1))) def c2(tau): # error: exp(b*w/eps) goes infinity even if erfc is zero # set above values to zero as it happens for large fast sedimenting particles ex = b * w / eps tm1 = 2 * (exp(tau / 2.) - 1) cc = np.zeros_like(ex) cc[ex < nn] = -exp(ex[ex < nn]) / (1 - b) * erfc((w[ex < nn] + b * tm1) / (2 * sqrt(eps * tm1))) return cc def c3(tau): # same as for c2 tm1 = 2 * (exp(tau / 2.) - 1) xxerfc = (w + tm1 * (2 - b)) / (2 * sqrt(eps * tm1)) ex = (w + tm1 * (1 - b)) / eps res = np.zeros_like(ex) res[ex < nn] = (2 - b) / (1 - b) * exp(ex[ex < nn]) * erfc(xxerfc[ex < nn]) return res # final meniscus part cexptovercfax = lambda tau: c1(tau) + c2(tau) + c3(tau) # bottom part epsb = 2 * Dt / (S * omega ** 2 * rb ** 2) d = 1 - epsb / 2. z = 2 * (r / rb - 1) c4 = lambda tau: -erfc((d * tau - z) / (2 * sqrt(epsb * tau))) c5 = lambda tau: -exp(d * z / epsb) / (1 - d) * erfc((-z - d * tau) / (2 * sqrt(epsb * tau))) c6 = lambda tau: (2 - d) / (1 - d) * exp(((1 - d) * tau + z) / epsb) * erfc( (-z - (2 - d) * tau) / (2 * sqrt(epsb * tau))) # final bottom part cexptovercbottom = lambda tau: c4(tau) + c5(tau) + c6(tau) timelist = dL() for tau in taulist: bottom = cexptovercbottom(tau) * c0 / 2. / exp(tau) meniscus = cexptovercfax(tau) * c0 / 2. / exp(tau) timelist.append(dA(np.c_[r * 1000, meniscus + bottom, meniscus, bottom].T)) timelist[-1].time = tau / (2 * S * omega ** 2) timelist[-1].rmeniscus = rm timelist[-1].rbottom = rb timelist[-1].w = w timelist[-1].Dt = Dt timelist[-1].c0 = c0 timelist[-1].viscosity = visc timelist[-1].sedimentation = S / svedberg timelist[-1].modelname = inspect.currentframe().f_code.co_name # timelist[-1].pelletfraction=1-scipy.integrate.simps(y=timelist[-1].Y, x=timelist[-1].X)/(max(r)*1000*c0) if Rh is not None: timelist[-1].Rh = Rh timelist[-1].columnname = 'position; concentration; conc_meniscus_part; conc_bottom_part' timelist[-1].setColumnIndex(iey=None) if len(timelist) == 1: return timelist[0] return timelist
[docs] def sedimentationCoefficient(M, partialVol=None, density=None, visc=None): r""" Sedimentation coefficient of a sphere in a solvent. :math:`S = M (1-\nu \rho)/(N_A 6\pi \eta R)` with :math:`V = 4/3\pi R^3 = \nu M` Parameters ---------- M : float Mass of the sphere or protein in units Da. partialVol : float Partial specific volume :math:`\nu` of the particle in units ml/g = l/kg. Default is 0.73 ml/g for proteins. density : float Density :math:`\rho` of the solvent in units g/ml=kg/l. Default is H2O at 293.15K visc : float Solvent viscosity :math:`\eta` in Pas. Default H2O at 293.15K Returns ------- float Sedimentation coefficient in units Svedberg (1S = 1e-13 sec ) """ if visc is None: visc = viscosity() if density is None: density = waterdensity('h2o1') if partialVol is None: partialVol = 0.73 # partial specific volume of proteins in ml/g m = M / constants.N_A * 0.001 # mass in kg Rh = (m * (partialVol * 0.001) * 3. / 4. / np.pi) ** (1 / 3.) # in units m return m * (1 - partialVol * density) / (6 * np.pi * visc * Rh) / 1e-13
[docs] def perrinFrictionFactor(p): r""" Perrin friction factor :math:`f_P` for ellipsoids of revolution for tranlational diffusion. Parameters ---------- p : array of float Axial ratio p=a/b with semiaxes `a` (along the axis of revolution) and b(=c) (equatorial semiaxis). - p>1 for prolate ellipsoids (cigar like) - 0<p<1 for oblate ellipsoids (disc like) Returns ------- Perrin friction factor : array Notes ----- Translational diffusion of a sphere is :math:`D_t=kT/f_{sphere}` with the sphere friction :math:`f_{sphere}=6\pi \eta R_h` of a sphere with hydrodynamic radius :math:`R_h`. For aspherical bodies like ellipsoids or proteins :math:`R_h` is an equivalent sphere radius showing the same :math:`D_t` . This was calculated by Perrin [2]_ for ellipsoids of revolution with semiaxes a,b(=c) .. math:: f_{sphere} = f_{sphere,R_h} f_P with the axial ratio :math:`p=a/b` (see [1]_ ) .. math:: f_P &= p^{2/3} \frac{\sqrt{1-1/p^2}}{ln((1+\sqrt{1-1/p^2})p)} &\text{ for p>1 prolate } &= p^{2/3} \frac{\sqrt{1/p^2-1}}{arctan(\sqrt{1/p^2-1})} &\text{ for p<1 oblate} The prefactor :math:`p^{2/3}` results from the conversion of the :math:`R_h` as a sphere with equivalent volume of the ellipsoid :math:`R_h=(3V_{ellipsoid}/4\pi)^{1/3}` and :math:`V_{ellipsoid}=\frac{4\pi}{3} ab^2 = \frac{4pi}{3} a^3/p^2` . Examples -------- Calculation :math:`R_g/R_h` for ellipsoids for simple shape analysis by comparing :math:`R_h` from DLS and :math:`R_g` from static light scattering or SAXS. :: import jscatter as js import numpy as np pp = np.r_[0.01:20:0.1] fp = js.formel.perrinFrictionFactor(pp) p= js.grace() p.plot(pp, fp , le='Perrin friction factor fp') Rh = lambda p: js.formel.perrinFrictionFactor(p) * (1/p**2)**(1/3) Rg = lambda p: (1/5*(1+2/pp**2))**0.5 p.plot(pp, Rg(pp) / Rh(pp) , le='Rg/Rh' ) p.plot([1,1],[0.5,1],li=1,sy=0) p.xaxis(label='axial ratio p') p.yaxis(label='Perrin friction factor ; Rg/Rh',min=0.5,max=2.5) p.legend(x=2,y=2.2) p.text(r'sphere R\sg\N/R\sh\N=0.7745',x=1.1,y=0.7) p.subtitle(r'Perrin friction factor and R\sg\N/R\sh\N \n for ellipsoids of revolution',size=1.5) # p.save(js.examples.imagepath+'/PerrinFrictionFactor.jpg') .. image:: ../../examples/images/PerrinFrictionFactor.jpg :align: center :height: 300px :alt: PerrinFrictionFactor References ---------- .. [1] On the hydrodynamic analysis of macromolecular conformation. Harding, S. E. Biophysical Chemistry, 55, 69–93 (1995) https://doi.org/10.1016/0301-4622(94)00143-8 .. [2] Mouvement brownien d’un ellipsoide-I. Dispersion diélectrique pour des molécules ellipsoidales. F Perrin J Phys-Paris 5, 497–511 (1934). """ assert np.any(p > 0), 'only for p > 0' fp = np.ones_like(p) # prolate a>b pp = p[p > 1] xi = (1 - 1 / pp ** 2) ** 0.5 fp[p > 1] = xi / np.log((1 + xi) * pp) # oblate a<b ; in Harding a and b are exchanged as always a>b is assumed (footnote on page 72) pp = p[p < 1] xi = (1 / pp ** 2 - 1) ** 0.5 fp[p < 1] = xi / np.arctan(xi) fp *= p ** (2 / 3) return fp