Source code for fluids.particle_size_distribution

"""Chemical Engineering Design Library (ChEDL). Utilities for process modeling.
Copyright (C) 2016, 2017, 2018, 2019, 2020 Caleb Bell <Caleb.Andrew.Bell@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

This module contains particle distribution characterization, fitting,
interpolating, and manipulation functions. It may be used with discrete
particle size distributions, or with statistical ones with parameters
specified.

For reporting bugs, adding feature requests, or submitting pull requests,
please use the `GitHub issue tracker <https://github.com/CalebBell/fluids/>`_
or contact the author at Caleb.Andrew.Bell@gmail.com.


.. contents:: :local:

Particle Size Distribution Base Class
-------------------------------------
.. autoclass:: ParticleSizeDistributionContinuous
    :members:

Discrete Particle Size Distributions
------------------------------------
.. autoclass:: ParticleSizeDistribution
    :members:
.. autoclass:: PSDInterpolated
    :members:

Statistical Particle Size Distributions
---------------------------------------
.. autoclass:: PSDLognormal
    :members:
.. autoclass:: PSDGatesGaudinSchuhman
    :members:
.. autoclass:: PSDRosinRammler
    :members:
.. autoclass:: PSDCustom
    :members:

Helper functions: Lognormal Distribution
----------------------------------------
.. autofunction:: pdf_lognormal
.. autofunction:: cdf_lognormal
.. autofunction:: pdf_lognormal_basis_integral

Helper functions: Gates Gaudin Schuhman Distribution
----------------------------------------------------
.. autofunction:: pdf_Gates_Gaudin_Schuhman
.. autofunction:: cdf_Gates_Gaudin_Schuhman
.. autofunction:: pdf_Gates_Gaudin_Schuhman_basis_integral

Helper functions: Rosin Rammler Distribution
--------------------------------------------
.. autofunction:: pdf_Rosin_Rammler
.. autofunction:: cdf_Rosin_Rammler
.. autofunction:: pdf_Rosin_Rammler_basis_integral

Sieves
------
.. autoclass:: Sieve
.. autodata:: ASTM_E11_sieves
.. autodata:: ISO_3310_1_sieves
.. autodata:: ISO_3310_1_R20
.. autodata:: ISO_3310_1_R20_3
.. autodata:: ISO_3310_1_R40_3
.. autodata:: ISO_3310_1_R10

Point Spacing
-------------
.. autofunction:: psd_spacing
"""

__all__ = ['ParticleSizeDistribution', 'ParticleSizeDistributionContinuous',
           'PSDLognormal', 'PSDGatesGaudinSchuhman', 'PSDRosinRammler',
           'PSDInterpolated', 'PSDCustom',
           'psd_spacing',

           'pdf_lognormal', 'cdf_lognormal', 'pdf_lognormal_basis_integral',

           'pdf_Gates_Gaudin_Schuhman', 'cdf_Gates_Gaudin_Schuhman',
           'pdf_Gates_Gaudin_Schuhman_basis_integral',

           'pdf_Rosin_Rammler', 'cdf_Rosin_Rammler',
           'pdf_Rosin_Rammler_basis_integral',

           'ASTM_E11_sieves', 'ISO_3310_1_sieves', 'Sieve',
           'ISO_3310_1_R20_3', 'ISO_3310_1_R20', 'ISO_3310_1_R10',
           'ISO_3310_1_R40_3']

from math import exp, log, log10, pi, sqrt

from fluids.numerics import brenth, cumsum, diff, epsilon, erf, gamma, gammaincc, linspace, logspace, normalize, quad

ROOT_TWO_PI = sqrt(2.0*pi)

NO_MATPLOTLIB_MSG = 'Optional dependency matplotlib is required for plotting'


[docs]class Sieve: r'''Class for storing data on sieves. If a property is not available, it is set to None. Attributes ---------- designation : str The standard name of the sieve - its opening's length in units of millimeters old_designation : str The older, imperial-esque name of the sieve; in Numbers, or inches for large sieves opening : float The opening length of the sieve holes, [m] opening_inch : float The opening length of the sieve holes in the rounded inches as stated in common tables (not exactly equal to the `opening`), [inch] Y_variation_avg : float The allowable average variation in the Y direction of the sieve openings, [m] X_variation_max : float The allowable maximum variation in the X direction of the sieve openings, [m] max_opening : float The maximum allowable opening of the sieve, [m] calibration_samples : float The number of opening sample inspections required for `calibration`- type sieve openings (per 100 ft^2 of sieve material), [1/(ft^2)] compliance_sd : float The maximum standard deviation of `compliance`-type sieve openings, [-] inspection_samples : float The number of opening sample inspections required for `inspection`- type sieve openings (based on an 8-inch sieve), [-] inspection_sd : float The maximum standard deviation of `inspection`-type sieve openings, [-] calibration_samples : float The number of opening sample inspections required for `calibration`- type sieve openings (based on an 8-inch sieve), [-] calibration_sd : float The maximum standard deviation of `calibration`-type sieve openings, [-] d_wire : float Typical wire diameter of the specified sieve size, [m] d_wire_min : float Permissible minimum wire diameter of specified sieve size, [m] d_wire_max : float Permissible maximum wire diameter of specified sieve size, [m] ''' __slots__ = ('designation', 'old_designation', 'opening', 'opening_inch', 'Y_variation_avg', 'X_variation_max', 'max_opening', 'calibration_samples', 'compliance_sd', 'inspection_samples', 'inspection_sd', 'calibration_samples', 'calibration_sd', 'd_wire', 'd_wire_min', 'd_wire_max', 'compliance_samples') # def __repr__(self): # s = 'Sieve(%s)' # s2 = '' # for attr, value in self.__dict__.items(): # if value is not None: # if type(value) == float: # value = round(value, 8) # elif type(value) == str: # value = "'" + value + "'" # s2 += '%s=%s, '%(attr, value) # s2 = s2[0:-2] # return s %(s2) def __repr__(self): return f'<Sieve, designation {self.designation} mm, opening {self.opening:g} m>' def __init__(self, designation, old_designation=None, opening=None, opening_inch=None, Y_variation_avg=None, X_variation_max=None, max_opening=None, compliance_samples=None, compliance_sd=None, inspection_samples=None, inspection_sd=None, calibration_samples=None, calibration_sd=None, d_wire=None, d_wire_min=None, d_wire_max=None): self.designation = designation self.old_designation = old_designation self.opening_inch = opening_inch self.opening = opening self.Y_variation_avg = Y_variation_avg self.X_variation_max = X_variation_max self.max_opening = max_opening self.compliance_samples = compliance_samples self.compliance_sd = compliance_sd self.inspection_samples = inspection_samples self.inspection_sd = inspection_sd self.calibration_samples = calibration_samples self.calibration_sd = calibration_sd self.d_wire = d_wire self.d_wire_min = d_wire_min self.d_wire_max = d_wire_max
ASTM_E11_sieves = {'0.02': Sieve(calibration_samples=300.0, d_wire_min=2e-08, d_wire=2e-08, inspection_sd=4.51, calibration_sd=4.75, old_designation='No. 635', opening=2e-05, compliance_samples=1000.0, opening_inch=8e-07, inspection_samples=100.0, designation='0.02', d_wire_max=2e-08, max_opening=0.035, X_variation_max=1.5e-05, Y_variation_avg=2.3e-06, compliance_sd=5.33), '0.025': Sieve(calibration_samples=300.0, d_wire_min=2e-08, d_wire=3e-08, inspection_sd=4.82, calibration_sd=5.06, old_designation='No. 500', opening=2.5e-05, compliance_samples=1000.0, opening_inch=1e-06, inspection_samples=100.0, designation='0.025', d_wire_max=3e-08, max_opening=0.041, X_variation_max=1.6e-05, Y_variation_avg=2.5e-06, compliance_sd=5.71), '0.032': Sieve(calibration_samples=300.0, d_wire_min=2e-08, d_wire=3e-08, inspection_sd=5.42, calibration_sd=5.71, old_designation='No. 450', opening=3.2e-05, compliance_samples=1000.0, opening_inch=1.2e-06, inspection_samples=100.0, designation='0.032', d_wire_max=3e-08, max_opening=0.05, X_variation_max=1.8e-05, Y_variation_avg=2.7e-06, compliance_sd=6.42), '0.038': Sieve(calibration_samples=300.0, d_wire_min=2e-08, d_wire=3e-08, inspection_sd=5.99, calibration_sd=6.31, old_designation='No. 400', opening=3.8e-05, compliance_samples=1000.0, opening_inch=1.5e-06, inspection_samples=100.0, designation='0.038', d_wire_max=3e-08, max_opening=0.058, X_variation_max=2e-05, Y_variation_avg=2.9e-06, compliance_sd=7.09), '0.045': Sieve(calibration_samples=250.0, d_wire_min=3e-08, d_wire=3e-08, inspection_sd=6.56, calibration_sd=6.84, old_designation='No. 325', opening=4.5e-05, compliance_samples=1000.0, opening_inch=1.7e-06, inspection_samples=100.0, designation='0.045', d_wire_max=4e-08, max_opening=0.067, X_variation_max=2.2e-05, Y_variation_avg=3.1e-06, compliance_sd=7.76), '0.053': Sieve(calibration_samples=250.0, d_wire_min=3e-08, d_wire=4e-08, inspection_sd=7.13, calibration_sd=7.44, old_designation='No. 270', opening=5.3e-05, compliance_samples=1000.0, opening_inch=2.1e-06, inspection_samples=100.0, designation='0.053', d_wire_max=4e-08, max_opening=0.077, X_variation_max=2.4e-05, Y_variation_avg=3.4e-06, compliance_sd=8.44), '0.063': Sieve(calibration_samples=250.0, d_wire_min=4e-08, d_wire=5e-08, inspection_sd=7.76, calibration_sd=8.09, old_designation='No. 230', opening=6.3e-05, compliance_samples=1000.0, opening_inch=2.5e-06, inspection_samples=100.0, designation='0.063', d_wire_max=5e-08, max_opening=0.089, X_variation_max=2.6e-05, Y_variation_avg=3.7e-06, compliance_sd=9.18), '0.075': Sieve(calibration_samples=250.0, d_wire_min=4e-08, d_wire=5e-08, inspection_sd=8.64, calibration_sd=9.02, old_designation='No. 200', opening=7.5e-05, compliance_samples=1000.0, opening_inch=2.9e-06, inspection_samples=100.0, designation='0.075', d_wire_max=6e-08, max_opening=0.104, X_variation_max=2.9e-05, Y_variation_avg=4.1e-06, compliance_sd=10.23), '0.09': Sieve(calibration_samples=200.0, d_wire_min=5e-08, d_wire=6e-08, inspection_sd=9.53, calibration_sd=9.8, old_designation='No. 170', opening=9e-05, compliance_samples=1000.0, opening_inch=3.5e-06, inspection_samples=100.0, designation='0.09', d_wire_max=7e-08, max_opening=0.122, X_variation_max=3.2e-05, Y_variation_avg=4.6e-06, compliance_sd=11.27), '0.106': Sieve(calibration_samples=200.0, d_wire_min=6e-08, d_wire=7e-08, inspection_sd=10.47, calibration_sd=10.77, old_designation='No. 140', opening=0.000106, compliance_samples=1000.0, opening_inch=4.1e-06, inspection_samples=100.0, designation='0.106', d_wire_max=8e-08, max_opening=0.141, X_variation_max=3.5e-05, Y_variation_avg=5.2e-06, compliance_sd=12.39), '0.125': Sieve(calibration_samples=200.0, d_wire_min=8e-08, d_wire=9e-08, inspection_sd=11.41, calibration_sd=11.74, old_designation='No. 120', opening=0.000125, compliance_samples=1000.0, opening_inch=4.9e-06, inspection_samples=100.0, designation='0.125', d_wire_max=1e-07, max_opening=0.163, X_variation_max=3.8e-05, Y_variation_avg=5.8e-06, compliance_sd=13.51), '0.15': Sieve(calibration_samples=200.0, d_wire_min=9e-08, d_wire=1e-07, inspection_sd=12.93, calibration_sd=13.3, old_designation='No. 100', opening=0.00015, compliance_samples=1000.0, opening_inch=5.9e-06, inspection_samples=100.0, designation='0.15', d_wire_max=1.2e-07, max_opening=0.193, X_variation_max=4.3e-05, Y_variation_avg=6.6e-06, compliance_sd=15.3), '0.18': Sieve(calibration_samples=200.0, d_wire_min=1.1e-07, d_wire=1.2e-07, inspection_sd=14.24, calibration_sd=14.65, old_designation='No. 80', opening=0.00018, compliance_samples=1000.0, opening_inch=7e-06, inspection_samples=100.0, designation='0.18', d_wire_max=1.5e-07, max_opening=0.227, X_variation_max=4.7e-05, Y_variation_avg=7.6e-06, compliance_sd=16.85), '0.212': Sieve(calibration_samples=160.0, d_wire_min=1.2e-07, d_wire=1.4e-07, inspection_sd=15.59, calibration_sd=16.08, old_designation='No. 70', opening=0.000212, compliance_samples=800.0, opening_inch=8.3e-06, inspection_samples=80.0, designation='0.212', d_wire_max=1.7e-07, max_opening=0.264, X_variation_max=5.2e-05, Y_variation_avg=8.7e-06, compliance_sd=18.79), '0.25': Sieve(calibration_samples=160.0, d_wire_min=1.3e-07, d_wire=1.6e-07, inspection_sd=17.44, calibration_sd=17.99, old_designation='No. 60', opening=0.00025, compliance_samples=800.0, opening_inch=9.8e-06, inspection_samples=80.0, designation='0.25', d_wire_max=1.9e-07, max_opening=0.308, X_variation_max=5.8e-05, Y_variation_avg=9.9e-06, compliance_sd=21.02), '0.3': Sieve(calibration_samples=160.0, d_wire_min=1.7e-07, d_wire=2e-07, inspection_sd=19.66, calibration_sd=20.29, old_designation='No. 50', opening=0.0003, compliance_samples=800.0, opening_inch=1.17e-05, inspection_samples=80.0, designation='0.3', d_wire_max=2.3e-07, max_opening=0.365, X_variation_max=6.5e-05, Y_variation_avg=1.15e-05, compliance_sd=23.7), '0.355': Sieve(calibration_samples=160.0, d_wire_min=1.9e-07, d_wire=2.2e-07, inspection_sd=21.95, calibration_sd=22.64, old_designation='No. 45', opening=0.000355, compliance_samples=800.0, opening_inch=1.39e-05, inspection_samples=80.0, designation='0.355', d_wire_max=2.6e-07, max_opening=0.427, X_variation_max=7.2e-05, Y_variation_avg=1.33e-05, compliance_sd=26.45), '0.425': Sieve(calibration_samples=120.0, d_wire_min=2.4e-07, d_wire=2.8e-07, inspection_sd=24.2, calibration_sd=25.08, old_designation='No. 40', opening=0.000425, compliance_samples=600.0, opening_inch=1.65e-05, inspection_samples=60.0, designation='0.425', d_wire_max=3.2e-07, max_opening=0.506, X_variation_max=8.1e-05, Y_variation_avg=1.55e-05, compliance_sd=29.95), '0.5': Sieve(calibration_samples=120.0, d_wire_min=2.7e-07, d_wire=3.2e-07, inspection_sd=26.85, calibration_sd=27.82, old_designation='No. 35', opening=0.0005, compliance_samples=600.0, opening_inch=1.97e-05, inspection_samples=60.0, designation='0.5', d_wire_max=3.6e-07, max_opening=0.589, X_variation_max=8.9e-05, Y_variation_avg=1.8e-05, compliance_sd=33.23), '0.6': Sieve(calibration_samples=100.0, d_wire_min=3.4e-07, d_wire=4e-07, inspection_sd=30.14, calibration_sd=31.32, old_designation='No. 30', opening=0.0006, compliance_samples=500.0, opening_inch=2.34e-05, inspection_samples=50.0, designation='0.6', d_wire_max=4.6e-07, max_opening=0.701, X_variation_max=0.000101, Y_variation_avg=2.12e-05, compliance_sd=38.0), '0.71': Sieve(calibration_samples=100.0, d_wire_min=3.8e-07, d_wire=4.5e-07, inspection_sd=33.82, calibration_sd=35.14, old_designation='No. 25', opening=0.00071, compliance_samples=500.0, opening_inch=2.78e-05, inspection_samples=50.0, designation='0.71', d_wire_max=5.2e-07, max_opening=0.822, X_variation_max=0.000112, Y_variation_avg=2.47e-05, compliance_sd=42.63), '0.85': Sieve(calibration_samples=80.0, d_wire_min=4.3e-07, d_wire=5e-07, inspection_sd=37.73, calibration_sd=39.36, old_designation='No. 20', opening=0.00085, compliance_samples=400.0, opening_inch=3.31e-05, inspection_samples=40.0, designation='0.85', d_wire_max=5.8e-07, max_opening=0.977, X_variation_max=0.000127, Y_variation_avg=2.91e-05, compliance_sd=48.76), '1': Sieve(calibration_samples=80.0, d_wire_min=0.00048, d_wire=0.00056, inspection_sd=0.042, calibration_sd=0.044, old_designation='No. 18', opening=0.001, compliance_samples=400.0, opening_inch=3.94e-05, inspection_samples=40.0, designation='1', d_wire_max=0.00064, max_opening=1.14, X_variation_max=0.00014, Y_variation_avg=3.4e-05, compliance_sd=0.055), '1.18': Sieve(calibration_samples=80.0, d_wire_min=0.00054, d_wire=0.00063, inspection_sd=0.049, calibration_sd=0.051, old_designation='No. 16', opening=0.00118, compliance_samples=400.0, opening_inch=4.69e-05, inspection_samples=40.0, designation='1.18', d_wire_max=0.00072, max_opening=1.34, X_variation_max=0.00016, Y_variation_avg=4e-05, compliance_sd=0.063), '1.4': Sieve(calibration_samples=80.0, d_wire_min=0.0006, d_wire=0.00071, inspection_sd=0.055, calibration_sd=0.057, old_designation='No. 14', opening=0.0014, compliance_samples=400.0, opening_inch=5.55e-05, inspection_samples=40.0, designation='1.4', d_wire_max=0.00082, max_opening=1.58, X_variation_max=0.00018, Y_variation_avg=4.6e-05, compliance_sd=0.071), '1.7': Sieve(calibration_samples=50.0, d_wire_min=0.00068, d_wire=0.0008, inspection_sd=0.059, calibration_sd=0.062, old_designation='No. 12', opening=0.0017, compliance_samples=250.0, opening_inch=6.61e-05, inspection_samples=25.0, designation='1.7', d_wire_max=0.00092, max_opening=1.9, X_variation_max=0.0002, Y_variation_avg=5.6e-05, compliance_sd=0.081), '100': Sieve(d_wire_min=0.0054, d_wire=0.0063, old_designation='4 in.', opening=0.1, compliance_samples=20.0, opening_inch=0.004, designation='100', d_wire_max=0.0072, max_opening=103.82, X_variation_max=0.00382, Y_variation_avg=0.00294), '106': Sieve(d_wire_min=0.0054, d_wire=0.0063, old_designation='4.24 in.', opening=0.106, compliance_samples=20.0, opening_inch=0.00424, designation='106', d_wire_max=0.0072, max_opening=109.99, X_variation_max=0.00399, Y_variation_avg=0.00312), '11.2': Sieve(calibration_samples=30.0, d_wire_min=0.0021, d_wire=0.0025, inspection_sd=0.256, calibration_sd=0.274, old_designation='7/16 in.', opening=0.0112, compliance_samples=150.0, opening_inch=0.000438, inspection_samples=15.0, designation='11.2', d_wire_max=0.0029, max_opening=11.97, X_variation_max=0.00077, Y_variation_avg=0.000346, compliance_sd=0.382), '12.5': Sieve(calibration_samples=30.0, d_wire_min=0.0021, d_wire=0.0025, inspection_sd=0.283, calibration_sd=0.302, old_designation='1/2 in.', opening=0.0125, compliance_samples=150.0, opening_inch=0.0005, inspection_samples=15.0, designation='12.5', d_wire_max=0.0029, max_opening=13.33, X_variation_max=0.00083, Y_variation_avg=0.000385, compliance_sd=0.421), '125': Sieve(d_wire_min=0.0068, d_wire=0.008, old_designation='5 in.', opening=0.125, compliance_samples=20.0, opening_inch=0.005, designation='125', d_wire_max=0.0092, max_opening=129.51, X_variation_max=0.00451, Y_variation_avg=0.00366), '13.2': Sieve(calibration_samples=30.0, d_wire_min=0.0024, d_wire=0.0028, inspection_sd=0.296, calibration_sd=0.316, old_designation='0.530 in.', opening=0.0132, compliance_samples=150.0, opening_inch=0.00053, inspection_samples=15.0, designation='13.2', d_wire_max=0.0032, max_opening=14.06, X_variation_max=0.00086, Y_variation_avg=0.000406, compliance_sd=0.441), '16': Sieve(calibration_samples=30.0, d_wire_min=0.0027, d_wire=0.00315, inspection_sd=0.354, calibration_sd=0.378, old_designation='5/8 in.', opening=0.016, compliance_samples=150.0, opening_inch=0.000625, inspection_samples=15.0, designation='16', d_wire_max=0.0036, max_opening=16.99, X_variation_max=0.00099, Y_variation_avg=0.00049, compliance_sd=0.527), '19': Sieve(calibration_samples=30.0, d_wire_min=0.0027, d_wire=0.00315, inspection_sd=0.418, calibration_sd=0.446, old_designation='3/4 in.', opening=0.019, compliance_samples=150.0, opening_inch=0.00075, inspection_samples=15.0, designation='19', d_wire_max=0.0035, max_opening=20.13, X_variation_max=0.00113, Y_variation_avg=0.000579, compliance_sd=0.622), '2': Sieve(calibration_samples=50.0, d_wire_min=0.00077, d_wire=0.0009, inspection_sd=0.068, calibration_sd=0.072, old_designation='No. 10', opening=0.002, compliance_samples=250.0, opening_inch=7.87e-05, inspection_samples=25.0, designation='2', d_wire_max=0.00104, max_opening=2.23, X_variation_max=0.00023, Y_variation_avg=6.5e-05, compliance_sd=0.094), '2.36': Sieve(calibration_samples=40.0, d_wire_min=0.00085, d_wire=0.001, inspection_sd=0.073, calibration_sd=0.077, old_designation='No. 8', opening=0.00236, compliance_samples=200.0, opening_inch=9.37e-05, inspection_samples=20.0, designation='2.36', d_wire_max=0.00115, max_opening=2.61, X_variation_max=0.00025, Y_variation_avg=7.6e-05, compliance_sd=0.104), '2.8': Sieve(calibration_samples=40.0, d_wire_min=0.00095, d_wire=0.00112, inspection_sd=0.085, calibration_sd=0.09, old_designation='No. 7', opening=0.0028, compliance_samples=200.0, opening_inch=0.00011, inspection_samples=20.0, designation='2.8', d_wire_max=0.0013, max_opening=3.09, X_variation_max=0.00029, Y_variation_avg=9e-05, compliance_sd=0.121), '22.4': Sieve(d_wire_min=0.003, d_wire=0.00355, inspection_sd=0.493, old_designation='7/8 in.', opening=0.0224, compliance_samples=150.0, opening_inch=0.000875, inspection_samples=15.0, designation='22.4', d_wire_max=0.0041, max_opening=23.67, X_variation_max=0.00127, Y_variation_avg=0.000681, compliance_sd=0.734), '25': Sieve(d_wire_min=0.003, d_wire=0.00355, inspection_sd=0.553, old_designation='1.00 in.', opening=0.025, compliance_samples=20.0, opening_inch=0.001, inspection_samples=15.0, designation='25', d_wire_max=0.0041, max_opening=26.38, X_variation_max=0.00138, Y_variation_avg=0.000758, compliance_sd=0.823), '26.5': Sieve(d_wire_min=0.003, d_wire=0.00355, inspection_sd=0.584, old_designation='1.06 in.', opening=0.0265, compliance_samples=20.0, opening_inch=0.00106, inspection_samples=15.0, designation='26.5', d_wire_max=0.0041, max_opening=27.94, X_variation_max=0.00144, Y_variation_avg=0.000802, compliance_sd=0.869), '3.35': Sieve(calibration_samples=40.0, d_wire_min=0.00106, d_wire=0.00125, inspection_sd=0.097, calibration_sd=0.103, old_designation='No. 6', opening=0.00335, compliance_samples=200.0, opening_inch=0.000132, inspection_samples=20.0, designation='3.35', d_wire_max=0.0015, max_opening=3.67, X_variation_max=0.00032, Y_variation_avg=0.000107, compliance_sd=0.138), '31.5': Sieve(d_wire_min=0.0034, d_wire=0.004, old_designation='1 1/4 in.', opening=0.0315, compliance_samples=20.0, opening_inch=0.00125, designation='31.5', d_wire_max=0.0046, max_opening=33.13, X_variation_max=0.00163, Y_variation_avg=0.00095, compliance_sd=1.066), '37.5': Sieve(d_wire_min=0.0038, d_wire=0.0045, old_designation='1 1/2 in.', opening=0.0375, compliance_samples=20.0, opening_inch=0.0015, designation='37.5', d_wire_max=0.0052, max_opening=39.35, X_variation_max=0.00185, Y_variation_avg=0.00113, compliance_sd=1.374), '4': Sieve(calibration_samples=30.0, d_wire_min=0.0012, d_wire=0.0014, inspection_sd=0.108, calibration_sd=0.115, old_designation='No. 5', opening=0.004, compliance_samples=150.0, opening_inch=0.000157, inspection_samples=15.0, designation='4', d_wire_max=0.0017, max_opening=4.37, X_variation_max=0.00037, Y_variation_avg=0.000127, compliance_sd=0.161), '4.75': Sieve(calibration_samples=30.0, d_wire_min=0.0013, d_wire=0.0016, inspection_sd=0.123, calibration_sd=0.131, old_designation='No. 4', opening=0.00475, compliance_samples=150.0, opening_inch=0.000187, inspection_samples=15.0, designation='4.75', d_wire_max=0.0019, max_opening=5.16, X_variation_max=0.00041, Y_variation_avg=0.00015, compliance_sd=0.182), '45': Sieve(d_wire_min=0.0038, d_wire=0.0045, old_designation='1 3/4 in.', opening=0.045, compliance_samples=20.0, opening_inch=0.00175, designation='45', d_wire_max=0.0052, max_opening=47.12, X_variation_max=0.00212, Y_variation_avg=0.00135), '5.6': Sieve(calibration_samples=30.0, d_wire_min=0.0013, d_wire=0.0016, inspection_sd=0.142, calibration_sd=0.151, old_designation='No. 3 1/2', opening=0.0056, compliance_samples=150.0, opening_inch=0.000223, inspection_samples=15.0, designation='5.6', d_wire_max=0.0019, max_opening=6.07, X_variation_max=0.00047, Y_variation_avg=0.000176, compliance_sd=0.211), '50': Sieve(d_wire_min=0.0043, d_wire=0.005, old_designation='2 in.', opening=0.05, compliance_samples=20.0, opening_inch=0.002, designation='50', d_wire_max=0.0058, max_opening=52.29, X_variation_max=0.00229, Y_variation_avg=0.00149), '53': Sieve(d_wire_min=0.0043, d_wire=0.005, old_designation='2.12 in.', opening=0.053, compliance_samples=20.0, opening_inch=0.00212, designation='53', d_wire_max=0.0058, max_opening=55.39, X_variation_max=0.00239, Y_variation_avg=0.00158), '6.3': Sieve(calibration_samples=30.0, d_wire_min=0.0015, d_wire=0.0018, inspection_sd=0.157, calibration_sd=0.167, old_designation='1/4 in.', opening=0.0063, compliance_samples=150.0, opening_inch=0.00025, inspection_samples=15.0, designation='6.3', d_wire_max=0.0021, max_opening=6.81, X_variation_max=0.00051, Y_variation_avg=0.000197, compliance_sd=0.233), '6.7': Sieve(calibration_samples=30.0, d_wire_min=0.0015, d_wire=0.0018, inspection_sd=0.164, calibration_sd=0.175, old_designation='0.265 in.', opening=0.0067, compliance_samples=150.0, opening_inch=0.000265, inspection_samples=15.0, designation='6.7', d_wire_max=0.0021, max_opening=7.23, X_variation_max=0.00053, Y_variation_avg=0.00021, compliance_sd=0.245), '63': Sieve(d_wire_min=0.0048, d_wire=0.0056, old_designation='2 1/2 in.', opening=0.063, compliance_samples=20.0, opening_inch=0.0025, designation='63', d_wire_max=0.0064, max_opening=65.71, X_variation_max=0.00271, Y_variation_avg=0.00187), '75': Sieve(d_wire_min=0.0054, d_wire=0.0063, old_designation='3 in.', opening=0.075, compliance_samples=20.0, opening_inch=0.003, designation='75', d_wire_max=0.0072, max_opening=78.09, X_variation_max=0.00309, Y_variation_avg=0.00222), '8': Sieve(calibration_samples=30.0, d_wire_min=0.0017, d_wire=0.002, inspection_sd=0.191, calibration_sd=0.204, old_designation='5/16 in.', opening=0.008, compliance_samples=150.0, opening_inch=0.000312, inspection_samples=15.0, designation='8', d_wire_max=0.0023, max_opening=8.6, X_variation_max=0.0006, Y_variation_avg=0.000249, compliance_sd=0.284), '9.5': Sieve(calibration_samples=30.0, d_wire_min=0.0019, d_wire=0.00224, inspection_sd=0.222, calibration_sd=0.237, old_designation='3/8 in.', opening=0.0095, compliance_samples=150.0, opening_inch=0.000375, inspection_samples=15.0, designation='9.5', d_wire_max=0.0026, max_opening=10.18, X_variation_max=0.00068, Y_variation_avg=0.000295, compliance_sd=0.33), '90': Sieve(d_wire_min=0.0054, d_wire=0.0063, old_designation='3 1/2 in.', opening=0.09, compliance_samples=20.0, opening_inch=0.0035, designation='90', d_wire_max=0.0072, max_opening=93.53, X_variation_max=0.00353, Y_variation_avg=0.00265) } """Dictionary containing ASTM E-11 sieve series :py:func:`Sieve` objects, indexed by their size in mm as a string. References ---------- .. [1] ASTM E11 - 17 - Standard Specification for Woven Wire Test Sieve Cloth and Test Sieves. """ ASTM_E11_sieve_designations = ['125', '106', '100', '90', '75', '63', '53', '50', '45', '37.5', '31.5', '26.5', '25', '22.4', '19', '16', '13.2', '12.5', '11.2', '9.5', '8', '6.7', '6.3', '5.6', '4.75', '4', '3.35', '2.8', '2.36', '2', '1.7', '1.4', '1.18', '1', '0.85', '0.71', '0.6', '0.5', '0.425', '0.355', '0.3', '0.25', '0.212', '0.18', '0.15', '0.125', '0.106', '0.09', '0.075', '0.063', '0.053', '0.045', '0.038', '0.032', '0.025', '0.02'] ASTM_E11_sieve_list = [ASTM_E11_sieves[i] for i in ASTM_E11_sieve_designations] ISO_3310_1_sieves = { '0.02': Sieve(designation='0.02', d_wire_max=2.3e-05, compliance_sd=4.7e-06, X_variation_max=1.3e-05, d_wire=2e-05, Y_variation_avg=2.1e-06, d_wire_min=2.3e-05, opening=2e-05), '0.025': Sieve(designation='0.025', d_wire_max=2.9e-05, compliance_sd=5.2e-06, X_variation_max=1.5e-05, d_wire=2.5e-05, Y_variation_avg=2.2e-06, d_wire_min=2.9e-05, opening=2.5e-05), '0.032': Sieve(designation='0.032', d_wire_max=3.3e-05, compliance_sd=5.9e-06, X_variation_max=1.7e-05, d_wire=2.8e-05, Y_variation_avg=2.4e-06, d_wire_min=3.3e-05, opening=3.2e-05), '0.036': Sieve(designation='0.036', d_wire_max=3.5e-05, compliance_sd=6.3e-06, X_variation_max=1.8e-05, d_wire=3e-05, Y_variation_avg=2.6e-06, d_wire_min=3.5e-05, opening=3.6e-05), '0.038': Sieve(designation='0.038', d_wire_max=3.5e-05, compliance_sd=6.4e-06, X_variation_max=1.8e-05, d_wire=3e-05, Y_variation_avg=2.6e-06, d_wire_min=3.5e-05, opening=3.8e-05), '0.04': Sieve(designation='0.04', d_wire_max=3.7e-05, compliance_sd=6.5e-06, X_variation_max=1.9e-05, d_wire=3.2e-05, Y_variation_avg=2.7e-06, d_wire_min=3.7e-05, opening=4e-05), '0.045': Sieve(designation='0.045', d_wire_max=3.7e-05, compliance_sd=6.9e-06, X_variation_max=2e-05, d_wire=3.2e-05, Y_variation_avg=2.8e-06, d_wire_min=3.7e-05, opening=4.5e-05), '0.05': Sieve(designation='0.05', d_wire_max=4.1e-05, compliance_sd=7.3e-06, X_variation_max=2.1e-05, d_wire=3.6e-05, Y_variation_avg=3e-06, d_wire_min=4.1e-05, opening=5e-05), '0.053': Sieve(designation='0.053', d_wire_max=4.1e-05, compliance_sd=7.6e-06, X_variation_max=2.1e-05, d_wire=3.6e-05, Y_variation_avg=3.1e-06, d_wire_min=4.1e-05, opening=5.3e-05), '0.056': Sieve(designation='0.056', d_wire_max=4.6e-05, compliance_sd=7.8e-06, X_variation_max=2.2e-05, d_wire=4e-05, Y_variation_avg=3.2e-06, d_wire_min=4.6e-05, opening=5.6e-05), '0.063': Sieve(designation='0.063', d_wire_max=5.2e-05, compliance_sd=8.3e-06, X_variation_max=2.4e-05, d_wire=4.5e-05, Y_variation_avg=3.4e-06, d_wire_min=5.2e-05, opening=6.3e-05), '0.071': Sieve(designation='0.071', d_wire_max=5.8e-05, compliance_sd=8.9e-06, X_variation_max=2.5e-05, d_wire=5e-05, Y_variation_avg=3.6e-06, d_wire_min=4.3e-05, opening=7.1e-05), '0.075': Sieve(designation='0.075', d_wire_max=5.8e-05, compliance_sd=9.1e-06, X_variation_max=2.6e-05, d_wire=5e-05, Y_variation_avg=3.7e-06, d_wire_min=4.3e-05, opening=7.5e-05), '0.08': Sieve(designation='0.08', d_wire_max=6.4e-05, compliance_sd=9.4e-06, X_variation_max=2.7e-05, d_wire=5.6e-05, Y_variation_avg=3.9e-06, d_wire_min=4.8e-05, opening=8e-05), '0.09': Sieve(designation='0.09', d_wire_max=7.2e-05, compliance_sd=1.01e-05, X_variation_max=2.9e-05, d_wire=6.3e-05, Y_variation_avg=4.2e-06, d_wire_min=5.4e-05, opening=9e-05), '0.1': Sieve(designation='0.1', d_wire_max=8.2e-05, compliance_sd=1.08e-05, X_variation_max=3e-05, d_wire=7.1e-05, Y_variation_avg=4.5e-06, d_wire_min=6e-05, opening=0.0001), '0.106': Sieve(designation='0.106', d_wire_max=8.2e-05, compliance_sd=1.11e-05, X_variation_max=3.1e-05, d_wire=7.1e-05, Y_variation_avg=4.7e-06, d_wire_min=6e-05, opening=0.000106), '0.112': Sieve(designation='0.112', d_wire_max=9.2e-05, compliance_sd=1.15e-05, X_variation_max=3.2e-05, d_wire=8e-05, Y_variation_avg=4.8e-06, d_wire_min=6.8e-05, opening=0.000112), '0.125': Sieve(designation='0.125', d_wire_max=0.000104, compliance_sd=1.22e-05, X_variation_max=3.4e-05, d_wire=9e-05, Y_variation_avg=5.2e-06, d_wire_min=7.7e-05, opening=0.000125), '0.14': Sieve(designation='0.14', d_wire_max=0.000115, compliance_sd=1.31e-05, X_variation_max=3.7e-05, d_wire=0.0001, Y_variation_avg=5.7e-06, d_wire_min=8.5e-05, opening=0.00014), '0.15': Sieve(designation='0.15', d_wire_max=0.000115, compliance_sd=1.37e-05, X_variation_max=3.8e-05, d_wire=0.0001, Y_variation_avg=6e-06, d_wire_min=8.5e-05, opening=0.00015), '0.16': Sieve(designation='0.16', d_wire_max=0.00013, compliance_sd=1.42e-05, X_variation_max=4e-05, d_wire=0.000112, Y_variation_avg=6.3e-06, d_wire_min=9.5e-05, opening=0.00016), '0.18': Sieve(designation='0.18', d_wire_max=0.00015, compliance_sd=1.53e-05, X_variation_max=4.3e-05, d_wire=0.000125, Y_variation_avg=6.8e-06, d_wire_min=0.000106, opening=0.00018), '0.2': Sieve(designation='0.2', d_wire_max=0.00017, compliance_sd=1.63e-05, X_variation_max=4.5e-05, d_wire=0.00014, Y_variation_avg=7.4e-06, d_wire_min=0.00012, opening=0.0002), '0.212': Sieve(designation='0.212', d_wire_max=0.00017, compliance_sd=1.69e-05, X_variation_max=4.7e-05, d_wire=0.00014, Y_variation_avg=7.8e-06, d_wire_min=0.00012, opening=0.000212), '0.224': Sieve(designation='0.224', d_wire_max=0.00019, compliance_sd=1.75e-05, X_variation_max=4.9e-05, d_wire=0.00016, Y_variation_avg=8.1e-06, d_wire_min=0.00013, opening=0.000224), '0.25': Sieve(designation='0.25', d_wire_max=0.00019, compliance_sd=1.88e-05, X_variation_max=5.2e-05, d_wire=0.00016, Y_variation_avg=8.9e-06, d_wire_min=0.00013, opening=0.00025), '0.28': Sieve(designation='0.28', d_wire_max=0.00021, compliance_sd=2.03e-05, X_variation_max=5.6e-05, d_wire=0.00018, Y_variation_avg=1e-05, d_wire_min=0.00015, opening=0.00028), '0.3': Sieve(designation='0.3', d_wire_max=0.00023, compliance_sd=2.12e-05, X_variation_max=5.8e-05, d_wire=0.0002, Y_variation_avg=1e-05, d_wire_min=0.00017, opening=0.0003), '0.315': Sieve(designation='0.315', d_wire_max=0.00023, compliance_sd=2.19e-05, X_variation_max=6e-05, d_wire=0.0002, Y_variation_avg=1.1e-05, d_wire_min=0.00017, opening=0.000315), '0.355': Sieve(designation='0.355', d_wire_max=0.00026, compliance_sd=2.37e-05, X_variation_max=6.5e-05, d_wire=0.000224, Y_variation_avg=1.2e-05, d_wire_min=0.00019, opening=0.000355), '0.4': Sieve(designation='0.4', d_wire_max=0.00029, compliance_sd=2.57e-05, X_variation_max=7e-05, d_wire=0.00025, Y_variation_avg=1.3e-05, d_wire_min=0.00021, opening=0.0004), '0.425': Sieve(designation='0.425', d_wire_max=0.00032, compliance_sd=2.68e-05, X_variation_max=7.3e-05, d_wire=0.00028, Y_variation_avg=1.4e-05, d_wire_min=0.00024, opening=0.000425), '0.45': Sieve(designation='0.45', d_wire_max=0.00032, compliance_sd=2.79e-05, X_variation_max=7.5e-05, d_wire=0.00028, Y_variation_avg=1.5e-05, d_wire_min=0.00024, opening=0.00045), '0.5': Sieve(designation='0.5', d_wire_max=0.00036, compliance_sd=3e-05, X_variation_max=8e-05, d_wire=0.000315, Y_variation_avg=1.6e-05, d_wire_min=0.00027, opening=0.0005), '0.56': Sieve(designation='0.56', d_wire_max=0.00041, compliance_sd=3.24e-05, X_variation_max=8.7e-05, d_wire=0.000355, Y_variation_avg=1.8e-05, d_wire_min=0.0003, opening=0.00056), '0.6': Sieve(designation='0.6', d_wire_max=0.00046, compliance_sd=3.4e-05, X_variation_max=9.1e-05, d_wire=0.0004, Y_variation_avg=1.9e-05, d_wire_min=0.00034, opening=0.0006), '0.63': Sieve(designation='0.63', d_wire_max=0.00046, compliance_sd=3.52e-05, X_variation_max=9.3e-05, d_wire=0.0004, Y_variation_avg=2e-05, d_wire_min=0.00034, opening=0.00063), '0.71': Sieve(designation='0.71', d_wire_max=0.00052, compliance_sd=3.84e-05, X_variation_max=0.000101, d_wire=0.00045, Y_variation_avg=2.2e-05, d_wire_min=0.00038, opening=0.00071), '0.8': Sieve(designation='0.8', d_wire_max=0.00052, compliance_sd=4.18e-05, X_variation_max=0.000109, d_wire=0.00045, Y_variation_avg=2.5e-05, d_wire_min=0.00038, opening=0.0008), '0.85': Sieve(designation='0.85', d_wire_max=0.00058, compliance_sd=4.36e-05, X_variation_max=0.000114, d_wire=0.0005, Y_variation_avg=2.6e-05, d_wire_min=0.00043, opening=0.00085), '0.9': Sieve(designation='0.9', d_wire_max=0.00058, compliance_sd=4.55e-05, X_variation_max=0.000118, d_wire=0.0005, Y_variation_avg=2.8e-05, d_wire_min=0.00043, opening=0.0009), '1': Sieve(designation='1', d_wire_max=0.00064, compliance_sd=4.9e-05, X_variation_max=0.00013, d_wire=0.00056, Y_variation_avg=3e-05, d_wire_min=0.00048, opening=0.001), '1.12': Sieve(designation='1.12', d_wire_max=0.00064, compliance_sd=5.3e-05, X_variation_max=0.00014, d_wire=0.00056, Y_variation_avg=3e-05, d_wire_min=0.00048, opening=0.00112), '1.18': Sieve(designation='1.18', d_wire_max=0.00072, compliance_sd=5.6e-05, X_variation_max=0.00014, d_wire=0.00063, Y_variation_avg=4e-05, d_wire_min=0.00054, opening=0.00118), '1.25': Sieve(designation='1.25', d_wire_max=0.00072, compliance_sd=5.8e-05, X_variation_max=0.00015, d_wire=0.00063, Y_variation_avg=4e-05, d_wire_min=0.00054, opening=0.00125), '1.4': Sieve(designation='1.4', d_wire_max=0.00082, compliance_sd=6.3e-05, X_variation_max=0.00016, d_wire=0.00071, Y_variation_avg=4e-05, d_wire_min=0.0006, opening=0.0014), '1.6': Sieve(designation='1.6', d_wire_max=0.00092, compliance_sd=7e-05, X_variation_max=0.00017, d_wire=0.0008, Y_variation_avg=5e-05, d_wire_min=0.00068, opening=0.0016), '1.7': Sieve(designation='1.7', d_wire_max=0.00092, compliance_sd=7.3e-05, X_variation_max=0.00018, d_wire=0.0008, Y_variation_avg=5e-05, d_wire_min=0.00068, opening=0.0017), '1.8': Sieve(designation='1.8', d_wire_max=0.00092, compliance_sd=7.6e-05, X_variation_max=0.00019, d_wire=0.0008, Y_variation_avg=5e-05, d_wire_min=0.00068, opening=0.0018), '10': Sieve(designation='10', d_wire_max=0.0029, compliance_sd=0.000307, X_variation_max=0.00064, d_wire=0.0025, Y_variation_avg=0.00028, d_wire_min=0.0021, opening=0.01), '100': Sieve(designation='100', d_wire_max=0.0072, X_variation_max=0.00344, d_wire=0.0063, Y_variation_avg=0.00265, d_wire_min=0.0054, opening=0.1), '106': Sieve(designation='106', d_wire_max=0.0072, X_variation_max=0.00359, d_wire=0.0063, Y_variation_avg=0.0028, d_wire_min=0.0054, opening=0.106), '11.2': Sieve(designation='11.2', d_wire_max=0.0029, compliance_sd=0.000339, X_variation_max=0.00069, d_wire=0.0025, Y_variation_avg=0.00031, d_wire_min=0.0021, opening=0.0112), '112': Sieve(designation='112', d_wire_max=0.0092, X_variation_max=0.00374, d_wire=0.008, Y_variation_avg=0.00296, d_wire_min=0.0068, opening=0.112), '12.5': Sieve(designation='12.5', d_wire_max=0.0029, compliance_sd=0.000374, X_variation_max=0.00075, d_wire=0.0025, Y_variation_avg=0.00035, d_wire_min=0.0021, opening=0.0125), '125': Sieve(designation='125', d_wire_max=0.0092, X_variation_max=0.00406, d_wire=0.008, Y_variation_avg=0.0033, d_wire_min=0.0068, opening=0.125), '13.2': Sieve(designation='13.2', d_wire_max=0.0032, compliance_sd=0.000392, X_variation_max=0.00078, d_wire=0.0028, Y_variation_avg=0.00037, d_wire_min=0.0024, opening=0.0132), '14': Sieve(designation='14', d_wire_max=0.0032, compliance_sd=0.000413, X_variation_max=0.00081, d_wire=0.0028, Y_variation_avg=0.00039, d_wire_min=0.0024, opening=0.014), '16': Sieve(designation='16', d_wire_max=0.0036, compliance_sd=0.000467, X_variation_max=0.00089, d_wire=0.00315, Y_variation_avg=0.00044, d_wire_min=0.0027, opening=0.016), '18': Sieve(designation='18', d_wire_max=0.0036, compliance_sd=0.00052, X_variation_max=0.00097, d_wire=0.00315, Y_variation_avg=0.00049, d_wire_min=0.0027, opening=0.018), '19': Sieve(designation='19', d_wire_max=0.0036, compliance_sd=0.000548, X_variation_max=0.00101, d_wire=0.00315, Y_variation_avg=0.00052, d_wire_min=0.0027, opening=0.019), '2': Sieve(designation='2', d_wire_max=0.00104, compliance_sd=8.3e-05, X_variation_max=0.0002, d_wire=0.0009, Y_variation_avg=6e-05, d_wire_min=0.00077, opening=0.002), '2.24': Sieve(designation='2.24', d_wire_max=0.00104, compliance_sd=9e-05, X_variation_max=0.00022, d_wire=0.0009, Y_variation_avg=7e-05, d_wire_min=0.00077, opening=0.00224), '2.36': Sieve(designation='2.36', d_wire_max=0.00115, compliance_sd=9.4e-05, X_variation_max=0.00023, d_wire=0.001, Y_variation_avg=7e-05, d_wire_min=0.00085, opening=0.00236), '2.5': Sieve(designation='2.5', d_wire_max=0.00115, compliance_sd=9.8e-05, X_variation_max=0.00024, d_wire=0.001, Y_variation_avg=7e-05, d_wire_min=0.00085, opening=0.0025), '2.8': Sieve(designation='2.8', d_wire_max=0.0013, compliance_sd=0.000108, X_variation_max=0.00026, d_wire=0.00112, Y_variation_avg=8e-05, d_wire_min=0.00095, opening=0.0028), '20': Sieve(designation='20', d_wire_max=0.0036, compliance_sd=0.000575, X_variation_max=0.00105, d_wire=0.00315, Y_variation_avg=0.00055, d_wire_min=0.0027, opening=0.02), '22.4': Sieve(designation='22.4', d_wire_max=0.0041, compliance_sd=0.000641, X_variation_max=0.00114, d_wire=0.00355, Y_variation_avg=0.00061, d_wire_min=0.003, opening=0.0224), '25': Sieve(designation='25', d_wire_max=0.0041, compliance_sd=0.000713, X_variation_max=0.00124, d_wire=0.00355, Y_variation_avg=0.00068, d_wire_min=0.003, opening=0.025), '26.5': Sieve(designation='26.5', d_wire_max=0.0041, compliance_sd=0.000757, X_variation_max=0.00129, d_wire=0.00355, Y_variation_avg=0.00072, d_wire_min=0.003, opening=0.0265), '28': Sieve(designation='28', d_wire_max=0.0041, compliance_sd=0.000801, X_variation_max=0.00135, d_wire=0.00355, Y_variation_avg=0.00076, d_wire_min=0.003, opening=0.028), '3.15': Sieve(designation='3.15', d_wire_max=0.0015, compliance_sd=0.000118, X_variation_max=0.00028, d_wire=0.00125, Y_variation_avg=9e-05, d_wire_min=0.00106, opening=0.00315), '3.35': Sieve(designation='3.35', d_wire_max=0.0015, compliance_sd=0.000124, X_variation_max=0.00029, d_wire=0.00125, Y_variation_avg=0.0001, d_wire_min=0.00106, opening=0.00335), '3.55': Sieve(designation='3.55', d_wire_max=0.0015, compliance_sd=0.00013, X_variation_max=0.0003, d_wire=0.00125, Y_variation_avg=0.0001, d_wire_min=0.00106, opening=0.00355), '31.5': Sieve(designation='31.5', d_wire_max=0.0046, compliance_sd=0.000905, X_variation_max=0.00147, d_wire=0.004, Y_variation_avg=0.00085, d_wire_min=0.0034, opening=0.0315), '35.5': Sieve(designation='35.5', d_wire_max=0.0046, compliance_sd=0.001, X_variation_max=0.0016, d_wire=0.004, Y_variation_avg=0.00096, d_wire_min=0.0034, opening=0.0355), '37.5': Sieve(designation='37.5', d_wire_max=0.0052, compliance_sd=0.001, X_variation_max=0.00167, d_wire=0.0045, Y_variation_avg=0.00101, d_wire_min=0.0038, opening=0.0375), '4': Sieve(designation='4', d_wire_max=0.0017, compliance_sd=0.000143, X_variation_max=0.00033, d_wire=0.0014, Y_variation_avg=0.00011, d_wire_min=0.0012, opening=0.004), '4.5': Sieve(designation='4.5', d_wire_max=0.0017, compliance_sd=0.000157, X_variation_max=0.00036, d_wire=0.0014, Y_variation_avg=0.00013, d_wire_min=0.0012, opening=0.0045), '4.75': Sieve(designation='4.75', d_wire_max=0.0019, compliance_sd=0.000164, X_variation_max=0.00037, d_wire=0.0016, Y_variation_avg=0.00014, d_wire_min=0.0013, opening=0.00475), '40': Sieve(designation='40', d_wire_max=0.0052, compliance_sd=0.001, X_variation_max=0.00175, d_wire=0.0045, Y_variation_avg=0.00108, d_wire_min=0.0038, opening=0.04), '45': Sieve(designation='45', d_wire_max=0.0052, compliance_sd=0.001, X_variation_max=0.00191, d_wire=0.0045, Y_variation_avg=0.00121, d_wire_min=0.0038, opening=0.045), '5': Sieve(designation='5', d_wire_max=0.0019, compliance_sd=0.000171, X_variation_max=0.00039, d_wire=0.0016, Y_variation_avg=0.00014, d_wire_min=0.0013, opening=0.005), '5.6': Sieve(designation='5.6', d_wire_max=0.0019, compliance_sd=0.000188, X_variation_max=0.00042, d_wire=0.0016, Y_variation_avg=0.00016, d_wire_min=0.0013, opening=0.0056), '50': Sieve(designation='50', d_wire_max=0.0058, X_variation_max=0.00206, d_wire=0.005, Y_variation_avg=0.00134, d_wire_min=0.0043, opening=0.05), '53': Sieve(designation='53', d_wire_max=0.0058, X_variation_max=0.00215, d_wire=0.005, Y_variation_avg=0.00142, d_wire_min=0.0043, opening=0.053), '56': Sieve(designation='56', d_wire_max=0.0058, X_variation_max=0.00224, d_wire=0.005, Y_variation_avg=0.0015, d_wire_min=0.0043, opening=0.056), '6.3': Sieve(designation='6.3', d_wire_max=0.0021, compliance_sd=0.000207, X_variation_max=0.00046, d_wire=0.0018, Y_variation_avg=0.00018, d_wire_min=0.0015, opening=0.0063), '6.7': Sieve(designation='6.7', d_wire_max=0.0021, compliance_sd=0.000218, X_variation_max=0.00048, d_wire=0.0018, Y_variation_avg=0.00019, d_wire_min=0.0015, opening=0.0067), '63': Sieve(designation='63', d_wire_max=0.0064, X_variation_max=0.00244, d_wire=0.0056, Y_variation_avg=0.00169, d_wire_min=0.0048, opening=0.063), '7.1': Sieve(designation='7.1', d_wire_max=0.0021, compliance_sd=0.000229, X_variation_max=0.0005, d_wire=0.0018, Y_variation_avg=0.0002, d_wire_min=0.0015, opening=0.0071), '71': Sieve(designation='71', d_wire_max=0.0064, X_variation_max=0.00267, d_wire=0.0056, Y_variation_avg=0.00189, d_wire_min=0.0048, opening=0.071), '75': Sieve(designation='75', d_wire_max=0.0072, X_variation_max=0.00278, d_wire=0.0063, Y_variation_avg=0.002, d_wire_min=0.0054, opening=0.075), '8': Sieve(designation='8', d_wire_max=0.0023, compliance_sd=0.000254, X_variation_max=0.00054, d_wire=0.002, Y_variation_avg=0.00022, d_wire_min=0.0017, opening=0.008), '80': Sieve(designation='80', d_wire_max=0.0072, X_variation_max=0.00291, d_wire=0.0063, Y_variation_avg=0.00213, d_wire_min=0.0054, opening=0.08), '9': Sieve(designation='9', d_wire_max=0.0026, compliance_sd=0.000281, X_variation_max=0.00059, d_wire=0.00224, Y_variation_avg=0.00025, d_wire_min=0.0019, opening=0.009), '9.5': Sieve(designation='9.5', d_wire_max=0.0026, compliance_sd=0.000294, X_variation_max=0.00061, d_wire=0.00224, Y_variation_avg=0.00027, d_wire_min=0.0019, opening=0.0095), '90': Sieve(designation='90', d_wire_max=0.0072, X_variation_max=0.00318, d_wire=0.0063, Y_variation_avg=0.00239, d_wire_min=0.0054, opening=0.09) } """Dictionary containing all of the individual :py:func:`Sieve` objects, on the ISO 3310-1:2016 series, indexed by their size in mm as a string. References ---------- .. [1] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. """ ISO_3310_1_sieve_designations = ['125', '112', '106', '100', '90', '80', '75', '71', '63', '56', '53', '50', '45', '40', '37.5', '35.5', '31.5', '28', '26.5', '25', '22.4', '20', '19', '18', '16', '14', '13.2', '12.5', '11.2', '10', '9.5', '9', '8', '7.1', '6.7', '6.3', '5.6', '5', '4.75', '4.5', '4', '3.55', '3.35', '3.15', '2.8', '2.5', '2.36', '2.24', '2', '1.8', '1.7', '1.6', '1.4', '1.25', '1.18', '1.12', '1', '0.9', '0.85', '0.8', '0.71', '0.63', '0.6', '0.56', '0.5', '0.45', '0.425', '0.4', '0.355', '0.315', '0.3', '0.28', '0.25', '0.224', '0.212', '0.2', '0.18', '0.16', '0.15', '0.14', '0.125', '0.112', '0.106', '0.1', '0.09', '0.08', '0.075', '0.071', '0.063', '0.056', '0.053', '0.05', '0.045', '0.04', '0.038', '0.036', '0.032', '0.025', '0.02'] ISO_3310_1_sieve_list = [ISO_3310_1_sieves[i] for i in ISO_3310_1_sieve_designations] ISO_3310_1_R20_3 = [ISO_3310_1_sieves[i] for i in ('125', '90', '63', '45', '31.5', '22.4', '16', '11.2', '8', '5.6', '4', '2.8', '2', '1.4', '1', '0.71', '0.5', '0.355', '0.25', '0.18', '0.125', '0.09', '0.063', '0.045')] """List containing all of the individual :py:func:`Sieve` objects, on the ISO 3310-1:2016 R20/3 series only, ordered from largest openings to smallest. References ---------- .. [1] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. """ ISO_3310_1_R20 = ['125', '112', '100', '90', '80', '71', '63', '56', '50', '45', '40', '35.5', '31.5', '28', '25', '22.4', '20', '18', '16', '14', '12.5', '11.2', '10', '9', '8', '7.1', '6.3', '5.6', '5', '4.5', '4', '3.55', '3.15', '2.8', '2.5', '2.24', '2', '1.8', '1.6', '1.4', '1.25', '1.12', '1', '0.9', '0.8', '0.71', '0.63', '0.56', '0.5', '0.45', '0.4', '0.355', '0.315', '0.28', '0.25', '0.224', '0.2', '0.18', '0.16', '0.14', '0.125', '0.112', '0.1', '0.09', '0.08', '0.071', '0.063', '0.056', '0.05', '0.045', '0.04', '0.036'] ISO_3310_1_R20 = [ISO_3310_1_sieves[i] for i in ISO_3310_1_R20] """List containing all of the individual :py:func:`Sieve` objects, on the ISO 3310-1:2016 R20 series only, ordered from largest openings to smallest. References ---------- .. [1] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. """ ISO_3310_1_R40_3 = ['125', '106', '90', '75', '63', '53', '45', '37.5', '31.5', '26.5', '22.4', '19', '16', '13.2', '11.2', '9.5', '8', '6.7', '5.6', '4.75', '4', '3.35', '2.8', '2.36', '2', '1.7', '1.4', '1.18', '1', '0.85', '0.71', '0.6', '0.5', '0.425', '0.355', '0.3', '0.25', '0.212', '0.18', '0.15', '0.125', '0.106', '0.09', '0.075', '0.063', '0.053', '0.045', '0.038'] ISO_3310_1_R40_3 = [ISO_3310_1_sieves[i] for i in ISO_3310_1_R40_3] """List containing all of the individual :py:func:`Sieve` objects, on the ISO 3310-1:2016 R40/3 series only, ordered from largest openings to smallest. References ---------- .. [1] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. """ ISO_3310_1_R10 = ['0.036', '0.032', '0.025', '0.02'] ISO_3310_1_R10 = [ISO_3310_1_sieves[i] for i in ISO_3310_1_R10] """List containing all of the individual :py:func:`Sieve` objects, on the ISO 3310-1:2016 R10 series only, ordered from largest openings to smallest. References ---------- .. [1] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. """ sieve_spacing_options = {'ISO 3310-1': ISO_3310_1_sieve_list, 'ISO 3310-1 R20': ISO_3310_1_R20, 'ISO 3310-1 R20/3': ISO_3310_1_R20_3, 'ISO 3310-1 R40/3': ISO_3310_1_R40_3, 'ISO 3310-1 R10': ISO_3310_1_R10, 'ASTM E11': ASTM_E11_sieve_list,}
[docs]def psd_spacing(d_min=None, d_max=None, pts=20, method='logarithmic'): r'''Create a particle spacing mesh in one of several ways for use in modeling discrete particle size distributions. The allowable meshes are 'linear', 'logarithmic', a geometric series specified by a Renard number such as 'R10', or the meshes available in one of several sieve standards. Parameters ---------- d_min : float, optional The minimum diameter at which the mesh starts, [m] d_max : float, optional The maximum diameter at which the mesh ends, [m] pts : int, optional The number of points to return for the mesh (note this is not respected by sieve meshes), [-] method : str, optional Either 'linear', 'logarithmic', a Renard number like 'R10' or 'R5' or 'R2.5', or one of the sieve standards 'ISO 3310-1 R40/3', 'ISO 3310-1 R20', 'ISO 3310-1 R20/3', 'ISO 3310-1', 'ISO 3310-1 R10', 'ASTM E11', [-] Returns ------- ds : list[float] The generated mesh diameters, [m] Notes ----- Note that when specifying a Renard series, only one of `d_min` or `d_max` can be respected! Provide only one of those numbers. Note that when specifying a sieve standard the number of points is not respected! Examples -------- >>> psd_spacing(d_min=5e-5, d_max=5e-4, method='ISO 3310-1 R20/3') [6.3e-05, 9e-05, 0.000125, 0.00018, 0.00025, 0.000355, 0.0005] References ---------- .. [1] ASTM E11 - 17 - Standard Specification for Woven Wire Test Sieve Cloth and Test Sieves. .. [2] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. ''' if d_min is not None: d_min = float(d_min) if d_max is not None: d_max = float(d_max) if method == 'logarithmic': return logspace(log10(d_min), log10(d_max), pts) elif method == 'linear': return linspace(d_min, d_max, pts) elif method[0] in ('R', 'r'): ratio = 10**(1.0/float(method[1:])) if d_min is not None and d_max is not None: raise ValueError('For geometric (Renard) series, only ' 'one of `d_min` and `d_max` should be provided') if d_min is not None: ds = [d_min] for i in range(pts-1): ds.append(ds[-1]*ratio) return ds elif d_max is not None: ds = [d_max] for i in range(pts-1): ds.append(ds[-1]/ratio) return list(reversed(ds)) elif method in sieve_spacing_options: l = sieve_spacing_options[method] ds = [] for sieve in l: if d_min <= sieve.opening <= d_max: ds.append(sieve.opening) return list(reversed(ds)) else: raise ValueError('Method not recognized')
[docs]def pdf_lognormal(d, d_characteristic, s): r'''Calculates the probability density function of a lognormal particle distribution given a particle diameter `d`, characteristic particle diameter `d_characteristic`, and distribution standard deviation `s`. .. math:: q(d) = \frac{1}{ds\sqrt{2\pi}} \exp\left[-0.5\left(\frac{ \ln(d/d_{characteristic})}{s}\right)^2\right] Parameters ---------- d : float Specified particle diameter, [m] d_characteristic : float Characteristic particle diameter; often D[3, 3] is used for this purpose but not always, [m] s : float Distribution standard deviation, [-] Returns ------- pdf : float Lognormal probability density function, [-] Notes ----- The characteristic diameter can be in terns of number density (denoted :math:`q_0(d)`), length density (:math:`q_1(d)`), surface area density (:math:`q_2(d)`), or volume density (:math:`q_3(d)`). Volume density is most often used. Interconversions among the distributions is possible but tricky. The standard distribution (i.e. the one used in Scipy) can perform the same computation with `d_characteristic` as the value of `scale`. >>> import scipy.stats >>> scipy.stats.lognorm.pdf(x=1E-4, s=1.1, scale=1E-5) 405.5420921 Scipy's calculation is over 300 times slower however, and this expression is numerically integrated so speed is required. Examples -------- >>> pdf_lognormal(d=1E-4, d_characteristic=1E-5, s=1.1) 405.5420921 References ---------- .. [1] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. ''' try: log_term = log(d/d_characteristic)/s except ValueError: return 0.0 return 1./(d*s*ROOT_TWO_PI)*exp(-0.5*log_term*log_term)
[docs]def cdf_lognormal(d, d_characteristic, s): r'''Calculates the cumulative distribution function of a lognormal particle distribution given a particle diameter `d`, characteristic particle diameter `d_characteristic`, and distribution standard deviation `s`. .. math:: Q(d) = 0.5\left(1 + \text{err}\left[\left(\frac{\ln(d/d_c)}{s\sqrt{2}} \right)\right]\right) Parameters ---------- d : float Specified particle diameter, [m] d_characteristic : float Characteristic particle diameter; often D[3, 3] is used for this purpose but not always, [m] s : float Distribution standard deviation, [-] Returns ------- cdf : float Lognormal cumulative density function, [-] Notes ----- The characteristic diameter can be in terns of number density (denoted :math:`q_0(d)`), length density (:math:`q_1(d)`), surface area density (:math:`q_2(d)`), or volume density (:math:`q_3(d)`). Volume density is most often used. Interconversions among the distributions is possible but tricky. The standard distribution (i.e. the one used in Scipy) can perform the same computation with `d_characteristic` as the value of `scale`. >>> import scipy.stats >>> scipy.stats.lognorm.cdf(x=1E-4, s=1.1, scale=1E-5) 0.9818369875798177 Scipy's calculation is over 100 times slower however. Examples -------- >>> cdf_lognormal(d=1E-4, d_characteristic=1E-5, s=1.1) 0.9818369875798 References ---------- .. [1] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. ''' try: return 0.5*(1.0 + erf((log(d/d_characteristic))/(s*sqrt(2.0)))) except: # math error at cdf = 0 (x going as low as possible) return 0.0
[docs]def pdf_lognormal_basis_integral(d, d_characteristic, s, n): r'''Calculates the integral of the multiplication of d^n by the lognormal pdf, given a particle diameter `d`, characteristic particle diameter `d_characteristic`, distribution standard deviation `s`, and exponent `n`. .. math:: \int d^n\cdot q(d)\; dd = -\frac{1}{2} \exp\left(\frac{s^2 n^2}{2} \right)d^n \left(\frac{d}{d_{characteristic}}\right)^{-n} \text{erf}\left[\frac{s^2 n - \ln(d/d_{characteristic})} {\sqrt{2} s} \right] This is the crucial integral required for interconversion between different bases such as number density (denoted :math:`q_0(d)`), length density (:math:`q_1(d)`), surface area density (:math:`q_2(d)`), or volume density (:math:`q_3(d)`). Parameters ---------- d : float Specified particle diameter, [m] d_characteristic : float Characteristic particle diameter; often D[3, 3] is used for this purpose but not always, [m] s : float Distribution standard deviation, [-] n : int Exponent of the multiplied n Returns ------- pdf_basis_integral : float Integral of lognormal pdf multiplied by d^n, [-] Notes ----- This integral has been verified numerically. This integral is itself integrated, so it is crucial to obtain an analytical form for at least this integral. Note overflow or zero division issues may occur for very large values of `s`, larger than 10. No mathematical limit was able to be obtained with a CAS. Examples -------- >>> pdf_lognormal_basis_integral(d=1E-4, d_characteristic=1E-5, s=1.1, n=-2) 56228306549.26362 ''' try: s2 = s*s t0 = exp(s2*n*n*0.5) d_ratio = d/d_characteristic t1 = (d/(d_ratio))**n t2 = erf((s2*n - log(d_ratio))/(sqrt(2.)*s)) return -0.5*t0*t1*t2 except (OverflowError, ZeroDivisionError, ValueError): return pdf_lognormal_basis_integral(d=1E-80, d_characteristic=d_characteristic, s=s, n=n)
[docs]def pdf_Gates_Gaudin_Schuhman(d, d_characteristic, m): r'''Calculates the probability density of a particle distribution following the Gates, Gaudin and Schuhman (GGS) model given a particle diameter `d`, characteristic (maximum) particle diameter `d_characteristic`, and exponent `m`. .. math:: q(d) = \frac{n}{d}\left(\frac{d}{d_{characteristic}}\right)^m \text{ if } d < d_{characteristic} \text{ else } 0 Parameters ---------- d : float Specified particle diameter, [m] d_characteristic : float Characteristic particle diameter; in this model, it is the largest particle size diameter in the distribution, [m] m : float Particle size distribution exponent, [-] Returns ------- pdf : float GGS probability density function, [-] Notes ----- The characteristic diameter can be in terns of number density (denoted :math:`q_0(d)`), length density (:math:`q_1(d)`), surface area density (:math:`q_2(d)`), or volume density (:math:`q_3(d)`). Volume density is most often used. Interconversions among the distributions is possible but tricky. Examples -------- >>> pdf_Gates_Gaudin_Schuhman(d=2E-4, d_characteristic=1E-3, m=2.3) 283.8355768512045 References ---------- .. [1] Schuhmann, R., 1940. Principles of Comminution, I-Size Distribution and Surface Calculations. American Institute of Mining, Metallurgical and Petroleum Engineers Technical Publication 1189. Mining Technology, volume 4, p. 1-11. .. [2] Bayat, Hossein, Mostafa Rastgo, Moharram Mansouri Zadeh, and Harry Vereecken. "Particle Size Distribution Models, Their Characteristics and Fitting Capability." Journal of Hydrology 529 (October 1, 2015): 872-89. ''' if d <= d_characteristic: return m/d*(d/d_characteristic)**m else: return 0.0
[docs]def cdf_Gates_Gaudin_Schuhman(d, d_characteristic, m): r'''Calculates the cumulative distribution function of a particle distribution following the Gates, Gaudin and Schuhman (GGS) model given a particle diameter `d`, characteristic (maximum) particle diameter `d_characteristic`, and exponent `m`. .. math:: Q(d) = \left(\frac{d}{d_{characteristic}}\right)^m \text{ if } d < d_{characteristic} \text{ else } 1 Parameters ---------- d : float Specified particle diameter, [m] d_characteristic : float Characteristic particle diameter; in this model, it is the largest particle size diameter in the distribution, [m] m : float Particle size distribution exponent, [-] Returns ------- cdf : float GGS cumulative density function, [-] Notes ----- The characteristic diameter can be in terns of number density (denoted :math:`q_0(d)`), length density (:math:`q_1(d)`), surface area density (:math:`q_2(d)`), or volume density (:math:`q_3(d)`). Volume density is most often used. Interconversions among the distributions is possible but tricky. Examples -------- >>> cdf_Gates_Gaudin_Schuhman(d=2E-4, d_characteristic=1E-3, m=2.3) 0.024681354508800397 References ---------- .. [1] Schuhmann, R., 1940. Principles of Comminution, I-Size Distribution and Surface Calculations. American Institute of Mining, Metallurgical and Petroleum Engineers Technical Publication 1189. Mining Technology, volume 4, p. 1-11. .. [2] Bayat, Hossein, Mostafa Rastgo, Moharram Mansouri Zadeh, and Harry Vereecken. "Particle Size Distribution Models, Their Characteristics and Fitting Capability." Journal of Hydrology 529 (October 1, 2015): 872-89. ''' if d <= d_characteristic: return (d/d_characteristic)**m else: return 1.0
[docs]def pdf_Gates_Gaudin_Schuhman_basis_integral(d, d_characteristic, m, n): r'''Calculates the integral of the multiplication of d^n by the Gates, Gaudin and Schuhman (GGS) model given a particle diameter `d`, characteristic (maximum) particle diameter `d_characteristic`, and exponent `m`. .. math:: \int d^n\cdot q(d)\; dd =\frac{m}{m+n} d^n \left(\frac{d} {d_{characteristic}}\right)^m Parameters ---------- d : float Specified particle diameter, [m] d_characteristic : float Characteristic particle diameter; in this model, it is the largest particle size diameter in the distribution, [m] m : float Particle size distribution exponent, [-] n : int Exponent of the multiplied n, [-] Returns ------- pdf_basis_integral : float Integral of Rosin Rammler pdf multiplied by d^n, [-] Notes ----- This integral does not have any numerical issues as `d` approaches 0. Examples -------- >>> pdf_Gates_Gaudin_Schuhman_basis_integral(d=2E-4, d_characteristic=1E-3, m=2.3, n=-3) -10136984887.543015 ''' return m/(m+n)*d**n*(d/d_characteristic)**m
[docs]def pdf_Rosin_Rammler(d, k, m): r'''Calculates the probability density of a particle distribution following the Rosin-Rammler (RR) model given a particle diameter `d`, and the two parameters `k` and `m`. .. math:: q(d) = k m d^{(m-1)} \exp(- k d^{m}) Parameters ---------- d : float Specified particle diameter, [m] k : float Parameter in the model, [(1/m)^m] m : float Parameter in the model, [-] Returns ------- pdf : float RR probability density function, [-] Notes ----- Examples -------- >>> pdf_Rosin_Rammler(1E-3, 200, 2) 0.3999200079994667 References ---------- .. [1] Rosin, P. "The Laws Governing the Fineness of Powdered Coal." J. Inst. Fuel. 7 (1933): 29-36. .. [2] Bayat, Hossein, Mostafa Rastgo, Moharram Mansouri Zadeh, and Harry Vereecken. "Particle Size Distribution Models, Their Characteristics and Fitting Capability." Journal of Hydrology 529 (October 1, 2015): 872-89. ''' return d**(m - 1.0)*k*m*exp(-d**m*k)
[docs]def cdf_Rosin_Rammler(d, k, m): r'''Calculates the cumulative distribution function of a particle distribution following the Rosin-Rammler (RR) model given a particle diameter `d`, and the two parameters `k` and `m`. .. math:: Q(d) = 1 - \exp\left(-k d^m\right) Parameters ---------- d : float Specified particle diameter, [m] k : float Parameter in the model, [(1/m)^m] m : float Parameter in the model, [-] Returns ------- cdf : float RR cumulative density function, [-] Notes ----- The characteristic diameter can be in terns of number density (denoted :math:`q_0(d)`), length density (:math:`q_1(d)`), surface area density (:math:`q_2(d)`), or volume density (:math:`q_3(d)`). Volume density is most often used. Interconversions among the distributions is possible but tricky. Examples -------- >>> cdf_Rosin_Rammler(5E-2, 200, 2) 0.3934693402873667 References ---------- .. [1] Rosin, P. "The Laws Governing the Fineness of Powdered Coal." J. Inst. Fuel. 7 (1933): 29-36. .. [2] Bayat, Hossein, Mostafa Rastgo, Moharram Mansouri Zadeh, and Harry Vereecken. "Particle Size Distribution Models, Their Characteristics and Fitting Capability." Journal of Hydrology 529 (October 1, 2015): 872-89. ''' return 1.0 - exp(-k*d**m)
[docs]def pdf_Rosin_Rammler_basis_integral(d, k, m, n): r'''Calculates the integral of the multiplication of d^n by the Rosin Rammler (RR) pdf, given a particle diameter `d`, and the two parameters `k` and `m`. .. math:: \int d^n\cdot q(d)\; dd =-d^{m+n} k(d^mk)^{-\frac{m+n}{m}}\Gamma \left(\frac{m+n}{m}\right)\text{gammaincc}\left[\left(\frac{m+n}{m} \right), kd^m\right] Parameters ---------- d : float Specified particle diameter, [m] k : float Parameter in the model, [(1/m)^m] m : float Parameter in the model, [-] n : int Exponent of the multiplied n, [-] Returns ------- pdf_basis_integral : float Integral of Rosin Rammler pdf multiplied by d^n, [-] Notes ----- This integral was derived using a CAS, and verified numerically. The `gammaincc` function is that from scipy.special, and `gamma` from the same. For very high powers of `n` or `m` when the diameter is very low, exceptions may occur. Examples -------- >>> "{:g}".format(pdf_Rosin_Rammler_basis_integral(5E-2, 200, 2, 3)) '-0.000452399' ''' # Also not able to compute the limit for d approaching 0. try: a = (m + n)/m x = d**m*k t1 = float(gamma(a))*float(gammaincc(a, x)) return (-d**(m+n)*k*(d**m*k)**(-a))*t1 except (OverflowError, ZeroDivisionError) as e: if d == 1E-40: raise e return pdf_Rosin_Rammler_basis_integral(1E-40, k, m, n)
names = {0: 'Number distribution', 1: 'Length distribution', 2: 'Area distribution', 3: 'Volume/Mass distribution'} def _label_distribution_n(n): # pragma: no cover if n in names: return names[n] else: return 'Order %s distribution' %str(n) _mean_size_docstring = r"""Calculates the mean particle size according to moment-ratio notation. This is the more common and often convenient definition. .. math:: \left[\bar D_{p,q} \right]^{(p-q)} = \frac{\sum_i n_i D_i^p } {\sum_i n_i D_i^q} \left[\bar D_{p,p} \right] = \exp\left[\frac{\sum_i n_i D_i^p\ln D_i }{\sum_i n_i D_i^p}\right] \text{, if p = q} Note that :math:`n_i` in the above equation is replaceable with the fraction of particles in that bin. Parameters ---------- p : int Power and/or subscript of D moment in the above equations, [-] q : int Power and/or subscript of D moment in the above equations, [-] Returns ------- d_pq : float Mean particle size according to the specified p and q, [m] Notes ----- The following is a list of common names for specific mean diameters. * **D[-3, 0]**: arithmetic harmonic mean volume diameter * **D[-2, 1]**: size-weighted harmonic mean volume diameter * **D[-1, 2]**: area-weighted harmonic mean volume diameter * **D[-2, 0]**: arithmetic harmonic mean area diameter * **D[-1, 1]**: size-weighted harmonic mean area diameter * **D[-1, 0]**: arithmetic harmonic mean diameter * **D[0, 0]**: arithmetic geometric mean diameter * **D[1, 1]**: size-weighted geometric mean diameter * **D[2, 2]**: area-weighted geometric mean diameter * **D[3, 3]**: volume-weighted geometric mean diameter * **D[1, 0]**: arithmetic mean diameter * **D[2, 1]**: size-weighted mean diameter * **D[3, 2]**: area-weighted mean diameter, **Sauter mean diameter** * **D[4, 3]**: volume-weighted mean diameter, **De Brouckere diameter** * **D[2, 0]**: arithmetic mean area diameter * **D[3, 1]**: size-weighted mean area diameter * **D[4, 2]**: area-weighted mean area diameter * **D[5, 3]**: volume-weighted mean area diameter * **D[3, 0]**: arithmetic mean volume diameter * **D[4, 1]**: size-weighted mean volume diameter * **D[5, 2]**: area-weighted mean volume diameter * **D[6, 3]**: volume-weighted mean volume diameter This notation was first introduced in [1]_. The sum of p and q is called the order of the mean size [3]_. .. math:: \bar D_{p,q} \equiv \bar D_{q, p} Examples -------- %s References ---------- .. [1] Mugele, R. A., and H. D. Evans. "Droplet Size Distribution in Sprays." Industrial & Engineering Chemistry 43, no. 6 (June 1951): 1317-24. https://doi.org/10.1021/ie50498a023. .. [2] ASTM E799 - 03(2015) - Standard Practice for Determining Data Criteria and Processing for Liquid Drop Size Analysis. .. [3] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. """ _mean_size_iso_docstring = r"""Calculates the mean particle size according to moment notation (ISO). This system is related to the moment-ratio notation as follows; see the `mean_size` method for the full formulas. .. math:: \bar x_{p-q, q} \equiv \bar x_{k+r, r} \equiv \bar D_{p,q} Parameters ---------- k : int Power and/or subscript of D moment in the above equations, [-] r : int Power and/or subscript of D moment in the above equations, [-] Returns ------- x_kr : float Mean particle size according to the specified k and r in the ISO series, [m] Notes ----- The following is a list of common names for specific mean diameters in the ISO naming convention. * **x[-3, 0]**: arithmetic harmonic mean volume diameter * **x[-3, 1]**: size-weighted harmonic mean volume diameter * **x[-3, 2]**: area-weighted harmonic mean volume diameter * **x[-2, 0]**: arithmetic harmonic mean area diameter * **x[-2, 1]**: size-weighted harmonic mean area diameter * **x[-1, 0]**: arithmetic harmonic mean diameter * **x[0, 0]**: arithmetic geometric mean diameter * **x[0, 1]**: size-weighted geometric mean diameter * **x[0, 2]**: area-weighted geometric mean diameter * **x[0, 3]**: volume-weighted geometric mean diameter * **x[1, 0]**: arithmetic mean diameter * **x[1, 1]**: size-weighted mean diameter * **x[1, 2]**: area-weighted mean diameter, **Sauter mean diameter** * **x[1, 3]**: volume-weighted mean diameter, **De Brouckere diameter** * **x[2, 0]**: arithmetic mean area diameter * **x[1, 1]**: size-weighted mean area diameter * **x[2, 2]**: area-weighted mean area diameter * **x[2, 3]**: volume-weighted mean area diameter * **x[3, 0]**: arithmetic mean volume diameter * **x[3, 1]**: size-weighted mean volume diameter * **x[3, 2]**: area-weighted mean volume diameter * **x[3, 3]**: volume-weighted mean volume diameter When working with continuous distributions, the ISO series must be used to perform the actual calculations. Examples -------- %s References ---------- .. [1] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. """
[docs]class ParticleSizeDistributionContinuous: r'''Base class representing a continuous particle size distribution specified by a mathematical/statistical function. This class holds the common methods only. Notes ----- Although the stated units of input are in meters, this class is actually independent of the units provided; all results will be consistent with the provided unit. Examples -------- Example problem from [1]_. >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6) References ---------- .. [1] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. ''' def _pdf_basis_integral_definite(self, d_min, d_max, n): # Needed as an api for numerical integrals return (self._pdf_basis_integral(d=d_max, n=n) - self._pdf_basis_integral(d=d_min, n=n))
[docs] def pdf(self, d, n=None): r'''Computes the probability density function of a continuous particle size distribution at a specified particle diameter, an optionally in a specified basis. The evaluation function varies with the distribution chosen. The interconversion between distribution orders is performed using the following formula [1]_: .. math:: q_s(d) = \frac{x^{(s-r)} q_r(d) dd} { \int_0^\infty d^{(s-r)} q_r(d) dd} Parameters ---------- d : float Particle size diameter, [m] n : int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer, [-] Returns ------- pdf : float The probability density function at the specified diameter and order, [-] Notes ----- The pdf order conversions are typically available analytically after some work. They have been verified numerically. See the various functions with names ending with 'basis_integral' for the formulations. The distributions normally do not have analytical limits for diameters of 0 or infinity, but large values suffice to capture the area of the integral. Examples -------- >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6, order=3) >>> psd.pdf(1e-5) 30522.765209509154 >>> psd.pdf(1e-5, n=3) 30522.765209509154 >>> psd.pdf(1e-5, n=0) 1238.661379483343 References ---------- .. [1] Masuda, Hiroaki, Ko Higashitani, and Hideto Yoshida. Powder Technology: Fundamentals of Particles, Powder Beds, and Particle Generation. CRC Press, 2006. ''' ans = self._pdf(d=d) if n is not None and n != self.order: power = n - self.order numerator = d**power*ans denominator = self._pdf_basis_integral_definite(d_min=0.0, d_max=self.d_excessive, n=power) ans = numerator/denominator # Handle splines which might go below zero ans = max(ans, 0.0) if self.truncated: if d < self.d_min or d > self.d_max: return 0.0 ans = (ans)/(self._cdf_d_max - self._cdf_d_min) return ans
[docs] def cdf(self, d, n=None): r'''Computes the cumulative distribution density function of a continuous particle size distribution at a specified particle diameter, an optionally in a specified basis. The evaluation function varies with the distribution chosen. .. math:: Q_n(d) = \int_0^d q_n(d) Parameters ---------- d : float Particle size diameter, [m] n : int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer, [-] Returns ------- cdf : float The cumulative distribution function at the specified diameter and order, [-] Notes ----- Analytical integrals can be found for most distributions even when order conversions are necessary. Examples -------- >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6, order=3) >>> [psd.cdf(5e-6, n) for n in range(4)] [0.933192798731, 0.8413447460685, 0.6914624612740, 0.5] ''' if n is not None and n != self.order: power = n - self.order # One of the pdf_basis_integral calls could be saved except for # support for numerical integrals numerator = self._pdf_basis_integral_definite(d_min=0.0, d_max=d, n=power) denominator = self._pdf_basis_integral_definite(d_min=0.0, d_max=self.d_excessive, n=power) ans = max(numerator/denominator, 0.0) # Handle splines which might go below zero else: ans = max(self._cdf(d=d), 0.0) if self.truncated: if d <= self.d_min: return 0.0 elif d >= self.d_max: return 1.0 ans = (ans - self._cdf_d_min)/(self._cdf_d_max - self._cdf_d_min) return ans
[docs] def delta_cdf(self, d_min, d_max, n=None): r'''Computes the difference in cumulative distribution function between two particle size diameters. .. math:: \Delta Q_n = Q_n(d_{max}) - Q_n(d_{min}) Parameters ---------- d_min : float Lower particle size diameter, [m] d_max : float Upper particle size diameter, [m] n : int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer, [-] Returns ------- delta_cdf : float The difference in the cumulative distribution function for the two diameters specified, [-] Examples -------- >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6, order=3) >>> psd.delta_cdf(1e-6, 1e-5) 0.9165280099853876 ''' return self.cdf(d_max, n=n) - self.cdf(d_min, n=n)
[docs] def dn(self, fraction, n=None): r'''Computes the diameter at which a specified `fraction` of the distribution falls under. Utilizes a bounded solver to search for the desired diameter. Parameters ---------- fraction : float Fraction of the distribution which should be under the calculated diameter, [-] n : int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer, [-] Returns ------- d : float Particle size diameter, [m] Examples -------- >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6, order=3) >>> psd.dn(.5) 5e-06 >>> psd.dn(1) 0.0002947436533523378 >>> psd.dn(0) 0.0 ''' if fraction == 1.0: # Avoid returning the maximum value of the search interval fraction = 1.0 - epsilon if fraction < 0: raise ValueError('Fraction must be more than 0') elif fraction == 0: # pragma: no cover if self.truncated: return self.d_min return 0.0 # Solve to float prevision limit - works well, but is there a real # point when with mpmath it would never happen? # dist.cdf(dist.dn(0)-1e-35) == 0 # dist.cdf(dist.dn(0)-1e-36) == input # dn(0) == 1.9663615597466143e-20 # def err(d): # cdf = self.cdf(d, n=n) # if cdf == 0: # cdf = -1 # return cdf # return brenth(err, self.d_minimum, self.d_excessive, maxiter=1000, xtol=1E-200) elif fraction > 1: raise ValueError('Fraction less than 1') # As the dn may be incredibly small, it is required for the absolute # tolerance to not be happy - it needs to continue iterating as long # as necessary to pin down the answer return brenth(lambda d:self.cdf(d, n=n) -fraction, self.d_minimum, self.d_excessive, maxiter=1000, xtol=1E-200)
[docs] def ds_discrete(self, d_min=None, d_max=None, pts=20, limit=1e-9, method='logarithmic'): r'''Create a particle spacing mesh to perform calculations with, according to one of several ways. The allowable meshes are 'linear', 'logarithmic', a geometric series specified by a Renard number such as 'R10', or the meshes available in one of several sieve standards. Parameters ---------- d_min : float, optional The minimum diameter at which the mesh starts, [m] d_max : float, optional The maximum diameter at which the mesh ends, [m] pts : int, optional The number of points to return for the mesh (note this is not respected by sieve meshes), [-] limit : float If `d_min` or `d_max` is not specified, it will be calculated as the `dn` at which this limit or 1-limit exists (this is ignored for Renard numbers), [-] method : str, optional Either 'linear', 'logarithmic', a Renard number like 'R10' or 'R5' or'R2.5', or one of the sieve standards 'ISO 3310-1 R40/3', 'ISO 3310-1 R20', 'ISO 3310-1 R20/3', 'ISO 3310-1', 'ISO 3310-1 R10', 'ASTM E11', [-] Returns ------- ds : list[float] The generated mesh diameters, [m] Notes ----- Note that when specifying a Renard series, only one of `d_min` or `d_max` can be respected! Provide only one of those numbers. Note that when specifying a sieve standard the number of points is not respected! References ---------- .. [1] ASTM E11 - 17 - Standard Specification for Woven Wire Test Sieve Cloth and Test Sieves. .. [2] ISO 3310-1:2016 - Test Sieves -- Technical Requirements and Testing -- Part 1: Test Sieves of Metal Wire Cloth. ''' if method[0] not in ('R', 'r'): if d_min is None: d_min = self.dn(limit) if d_max is None: d_max = self.dn(1.0 - limit) return psd_spacing(d_min=d_min, d_max=d_max, pts=pts, method=method)
[docs] def fractions_discrete(self, ds, n=None): r'''Computes the fractions of the cumulative distribution functions which lie between the specified specified particle diameters. The first diameter contains the cdf from 0 to it. Parameters ---------- ds : list[float] Particle size diameters, [m] n : int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer, [-] Returns ------- fractions : float The differences in the cumulative distribution functions at the specified diameters and order, [-] Examples -------- >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6, order=3) >>> psd.fractions_discrete([1e-6, 1e-5, 1e-4, 1e-3]) [0.00064347101291, 0.916528009985, 0.0828285179619, 1.039798e-09] ''' cdfs = [self.cdf(d, n=n) for d in ds] return [cdfs[0]] + diff(cdfs)
[docs] def cdf_discrete(self, ds, n=None): r'''Computes the cumulative distribution functions for a list of specified particle diameters. Parameters ---------- ds : list[float] Particle size diameters, [m] n : int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer, [-] Returns ------- cdfs : float The cumulative distribution functions at the specified diameters and order, [-] Examples -------- >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6, order=3) >>> psd.cdf_discrete([1e-6, 1e-5, 1e-4, 1e-3]) [0.000643471012913, 0.917171480998, 0.999999998960, 1.0] ''' return [self.cdf(d, n=n) for d in ds]
[docs] def mean_size(self, p, q): ''' >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6) >>> psd.mean_size(3, 2) 4.412484512922977e-06 Note that for the case where p == q, a different set of formulas are required - which do not have analytical results for many distributions. Therefore, a close numerical approximation is used instead, to perturb the values of p and q so they are 1E-9 away from each other. This leads only to slight errors, as in the example below where the correct answer is 5E-6. >>> psd.mean_size(3, 3) 4.9999999304923345e-06 ''' if p == q: p -= 1e-9 q += 1e-9 pow1 = q - self.order denominator = self._pdf_basis_integral_definite(d_min=self.d_minimum, d_max=self.d_excessive, n=pow1) root_power = p - q pow3 = p - self.order numerator = self._pdf_basis_integral_definite(d_min=self.d_minimum, d_max=self.d_excessive, n=pow3) return (numerator/denominator)**(1.0/(root_power))
[docs] def mean_size_ISO(self, k, r): ''' >>> psd = PSDLognormal(s=0.5, d_characteristic=5E-6) >>> psd.mean_size_ISO(1, 2) 4.412484512922977e-06 ''' p = k + r q = r return self.mean_size(p=p, q=q)
@property def vssa(self): r'''The volume-specific surface area of a particle size distribution. .. math:: \text{VSSA} = \frac{6}{\bar x_{1,2}} Returns ------- VSSA : float The volume-specific surface area of the distribution, [m^2/m^3] Examples -------- >>> PSDLognormal(s=0.5, d_characteristic=5E-6).vssa 1359778.1436801916 References ---------- .. [1] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. ''' return 6/self.mean_size(3, 2)
[docs] def plot_pdf(self, n=(0, 1, 2, 3), d_min=None, d_max=None, pts=500, normalized=False, method='linear'): # pragma: no cover r'''Plot the probability density function of the particle size distribution. The plotted range can be specified using `d_min` and `d_max`, or estimated automatically. One or more order can be plotted, by providing an iterable of ints as the value of `n` or just one int. Parameters ---------- n : tuple(int) or int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer; as many as desired may be specified, [-] d_min : float, optional Lower particle size diameter, [m] d_max : float, optional Upper particle size diameter, [m] pts : int, optional The number of points for values to be calculated, [-] normalized : bool, optional Whether to display the actual probability density function, which may have a huge magnitude - or to divide each point by the sum of all the points. Doing this is a common practice, but the values at each point are dependent on the number of points being plotted, and the distribution of the points; [-] method : str, optional Either 'linear', 'logarithmic', a Renard number like 'R10' or 'R5' or'R2.5', or one of the sieve standards 'ISO 3310-1 R40/3', 'ISO 3310-1 R20', 'ISO 3310-1 R20/3', 'ISO 3310-1', 'ISO 3310-1 R10', 'ASTM E11', [-] ''' try: import matplotlib.pyplot as plt except: # pragma: no cover raise ValueError(NO_MATPLOTLIB_MSG) ds = self.ds_discrete(d_min=d_min, d_max=d_max, pts=pts, method=method) try: for ni in n: fractions = [self.pdf(d, n=ni) for d in ds] if normalized: fractions = normalize(fractions) plt.semilogx(ds, fractions, label=_label_distribution_n(ni)) except Exception: fractions = [self.pdf(d, n=n) for d in ds] if normalized: fractions = normalize(fractions) plt.semilogx(ds, fractions, label=_label_distribution_n(n)) plt.ylabel('Probability density function, [-]') plt.xlabel('Particle diameter, [m]') plt.title('Probability density function of {} distribution with ' 'parameters {}'.format(self.name, self.parameters)) plt.legend() plt.show() return fractions
[docs] def plot_cdf(self, n=(0, 1, 2, 3), d_min=None, d_max=None, pts=500, method='logarithmic'): # pragma: no cover r'''Plot the cumulative distribution function of the particle size distribution. The plotted range can be specified using `d_min` and `d_max`, or estimated automatically. One or more order can be plotted, by providing an iterable of ints as the value of `n` or just one int. Parameters ---------- n : tuple(int) or int, optional None (for the `order` specified when the distribution was created), 0 (number), 1 (length), 2 (area), 3 (volume/mass), or any integer; as many as desired may be specified, [-] d_min : float, optional Lower particle size diameter, [m] d_max : float, optional Upper particle size diameter, [m] pts : int, optional The number of points for values to be calculated, [-] method : str, optional Either 'linear', 'logarithmic', a Renard number like 'R10' or 'R5' or'R2.5', or one of the sieve standards 'ISO 3310-1 R40/3', 'ISO 3310-1 R20', 'ISO 3310-1 R20/3', 'ISO 3310-1', 'ISO 3310-1 R10', 'ASTM E11', [-] ''' try: import matplotlib.pyplot as plt except: # pragma: no cover raise ValueError(NO_MATPLOTLIB_MSG) ds = self.ds_discrete(d_min=d_min, d_max=d_max, pts=pts, method=method) try: for ni in n: cdfs = self.cdf_discrete(ds=ds, n=ni) plt.semilogx(ds, cdfs, label=_label_distribution_n(ni)) except: cdfs = self.cdf_discrete(ds=ds, n=n) plt.semilogx(ds, cdfs, label=_label_distribution_n(n)) if self.points: plt.plot(self.ds, self.fraction_cdf, '+', label='Volume/Mass points') if hasattr(self, 'area_fractions'): plt.plot(self.ds, cumsum(self.area_fractions), '+', label='Area points') if hasattr(self, 'length_fractions'): plt.plot(self.ds, cumsum(self.length_fractions), '+', label='Length points') if hasattr(self, 'number_fractions'): plt.plot(self.ds, cumsum(self.number_fractions), '+', label='Number points') plt.ylabel('Cumulative density function, [-]') plt.xlabel('Particle diameter, [m]') plt.title('Cumulative density function of {} distribution with ' 'parameters {}'.format(self.name, self.parameters)) plt.legend() plt.show()
[docs]class ParticleSizeDistribution(ParticleSizeDistributionContinuous): r'''Class representing a discrete particle size distribution specified by a series of diameter bins, and the quantity of particles in each bin. The quantities may be specified as either the fraction of particles in each bin, or as cumulative distributions. The input fractions can be specified to be in a mass basis (`order=3`), number basis (`order=0`), or the orders in between for length basis or area basis. If the fractions do not sum to 1, and `cdf` is False, then the fractions are normalized. This allows flow rates or counts of size bins to be given as well. Parameters ---------- ds : list[float] Diameter bins; length of the specified quantities, optionally +1 that length to specify a cutoff diameter for the smallest diameter bin, [m] fractions : list[float], optional The mass/mole/volume/length/area/count fractions or cumulative distributions or counts of each particle size in each diameter bin (the type is specified by `order`), [-] order : int, optional 0 for a number distribution as input; 1 for length distribution; 2 for area distribution; 3 for mass, mole, or volume distribution, [-] cdf : bool, optional If the distribution is given as increasing fractions with 1 as the last result, `cdf` must be set to True, [-] monotonic : bool, optional If True, for interpolated quanties, monotonic splines will be used instead of the standard splines, [-] Attributes ---------- fractions : list[float] The mass/mole/volume basis fractions of particles in each bin, [-] area_fractions : list[float] The area fractions of particles in each bin, [-] length_fractions : list[float] The length fractions of particles in each bin, [-] number_fractions : list[float] The number fractions of particles in each bin, [-] fraction_cdf : list[float] The cumulative mass/mole/volume basis fractions of particles in each bin, [-] area_cdf : list[float] The cumulative area fractions of particles in each bin, [-] length_cdf : list[float] The cumulative length fractions of particles in each bin, [-] number_cdf : list[float] The cumulative number fractions of particles in each bin, [-] size_classes : bool Whether or not the diameter bins were set as size classes (as length of fractions + 1), [-] N : int The number of provided points, [-] Notes ----- Although the stated units of input are in meters, this class is actually independent of the units provided; all results will be consistent with the provided unit. Examples -------- Example problem from [1]_, calculating several diameters and the cumulative distribution. >>> import numpy as np >>> ds = 1E-6*np.array([240, 360, 450, 562.5, 703, 878, 1097, 1371, 1713, 2141, 2676, 3345, 4181, 5226, 6532]) >>> numbers = [65, 119, 232, 410, 629, 849, 990, 981, 825, 579, 297, 111, 21, 1] >>> psd = ParticleSizeDistribution(ds=ds, fractions=numbers, order=0) >>> psd <Particle Size Distribution, points=14, D[3, 3]=0.002451 m> References ---------- .. [1] ASTM E799 - 03(2015) - Standard Practice for Determining Data Criteria and Processing for Liquid Drop Size Analysis. .. [2] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. ''' def __repr__(self): txt = '<Particle Size Distribution, points=%d, D[3, 3]=%f m>' return txt %(self.N, self.mean_size(p=3, q=3)) size_classes = False _interpolated = None points = True truncated = False name = 'Discrete' def __init__(self, ds, fractions, cdf=False, order=3, monotonic=True): self.monotonic = monotonic self.ds = ds self.order = order if ds is not None and (len(ds) == len(fractions) + 1): self.size_classes = True else: self.size_classes = False if cdf: # Convert a cdf to fraction set if len(fractions)+1 == len(ds): fractions = [fractions[0]] + diff(fractions) else: fractions = diff(fractions) fractions.insert(0, 0.0) elif sum(fractions) != 1.0: # Normalize flow inputs tot_inv = 1.0/sum(fractions) fractions = [i*tot_inv if i != 0.0 else 0.0 for i in fractions] self.N = len(fractions) # This will always be in base-3 basis if self.order != 3: power = 3 - self.order d3s = [self.di_power(i, power=power)*fractions[i] for i in range(self.N)] tot_d3 = sum(d3s) self.fractions = [i/tot_d3 for i in d3s] else: self.fractions = fractions # Set the number fractions D3s = [self.di_power(i, power=3) for i in range(self.N)] numbers = [Vi/Vp for Vi, Vp in zip(self.fractions, D3s)] number_sum = sum(numbers) self.number_fractions = [i/number_sum for i in numbers] # Set the length fractions D3s = [self.di_power(i, power=2) for i in range(self.N)] numbers = [Vi/Vp for Vi, Vp in zip(self.fractions, D3s)] number_sum = sum(numbers) self.length_fractions = [i/number_sum for i in numbers] # Set the surface area fractions D3s = [self.di_power(i, power=1) for i in range(self.N)] numbers = [Vi/Vp for Vi, Vp in zip(self.fractions, D3s)] number_sum = sum(numbers) self.area_fractions = [i/number_sum for i in numbers] # Things for interoperability with the Continuous distribution self.d_excessive = self.ds[-1] self.d_minimum = 0.0 self.parameters = {} self.order = 3 self.fraction_cdf = self.volume_cdf = cumsum(self.fractions) self.area_cdf = cumsum(self.area_fractions) self.length_cdf = cumsum(self.length_fractions) self.number_cdf = cumsum(self.number_fractions) @property def interpolated(self): if not self._interpolated: self._interpolated = PSDInterpolated(ds=self.ds, fractions=self.fractions, order=3, monotonic=self.monotonic) return self._interpolated def _pdf(self, d): return self.interpolated._pdf(d) def _cdf(self, d): return self.interpolated._cdf(d) def _pdf_basis_integral(self, d, n): return self.interpolated._pdf_basis_integral(d, n) def _fit_obj_function(self, vals, distribution, n): err = 0.0 dist = distribution(*list(vals)) l = len(self.fractions) if self.size_classes else len(self.fractions) - 1 for i in range(l): delta_cdf = dist.delta_cdf(d_min=self.ds[i], d_max=self.ds[i+1]) err += abs(delta_cdf - self.fractions[i]) return err
[docs] def fit(self, x0=None, distribution='lognormal', n=None, **kwargs): """Incomplete method to fit experimental values to a curve. It is very hard to get good initial guesses, which are really required for this. Differential evolution is promising. This API is likely to change in the future. """ dist = {'lognormal': PSDLognormal, 'GGS': PSDGatesGaudinSchuhman, 'RR': PSDRosinRammler}[distribution] if distribution == 'lognormal': if x0 is None: d_characteristic = sum([fi*di for fi, di in zip(self.fractions, self.Dis)]) s = 0.4 x0 = [d_characteristic, s] elif distribution == 'GGS': if x0 is None: d_characteristic = sum([fi*di for fi, di in zip(self.fractions, self.Dis)]) m = 1.5 x0 = [d_characteristic, m] elif distribution == 'RR' and x0 is None: x0 = [5E-6, 1e-2] from scipy.optimize import minimize return minimize(self._fit_obj_function, x0, args=(dist, n), **kwargs)
@property def Dis(self): """Representative diameters of each bin.""" return [self.di_power(i, power=1) for i in range(self.N)]
[docs] def di_power(self, i, power=1): r'''Method to calculate a power of a particle class/bin in a generic way so as to support when there are as many `ds` as `fractions`, or one more diameter spec than `fractions`. When each bin has a lower and upper bound, the formula is as follows [1]_. .. math:: D_i^r = \frac{D_{i, ub}^{(r+1)} - D_{i, lb}^{(r+1)}} {(D_{i, ub} - D_{i, lb})(r+1)} Where `ub` represents the upper bound, and `lb` represents the lower bound. Otherwise, the standard definition is used: .. math:: D_i^r = D_i^r Parameters ---------- i : int The index of the diameter for the calculation, [-] power : int The exponent, [-] Returns ------- di_power : float The representative bin diameter raised to `power`, [m^power] References ---------- .. [1] ASTM E799 - 03(2015) - Standard Practice for Determining Data Criteria and Processing for Liquid Drop Size Analysis. ''' if self.size_classes: rt = power + 1 return ((self.ds[i+1]**rt - self.ds[i]**rt)/((self.ds[i+1] - self.ds[i])*rt)) else: return self.ds[i]**power
[docs] def mean_size(self, p, q): ''' >>> import numpy as np >>> ds = 1E-6*np.array([240, 360, 450, 562.5, 703, 878, 1097, 1371, 1713, 2141, 2676, 3345, 4181, 5226, 6532]) >>> numbers = [65, 119, 232, 410, 629, 849, 990, 981, 825, 579, 297, 111, 21, 1] >>> psd = ParticleSizeDistribution(ds=ds, fractions=numbers, order=0) >>> psd.mean_size(3, 2) 0.002269321031745045 ''' if p != q: # Note: D(p, q) = D(q, p); in ISO and proven experimentally numerator = sum(self.di_power(i=i, power=p)*self.number_fractions[i] for i in range(self.N)) denominator = sum(self.di_power(i=i, power=q)*self.number_fractions[i] for i in range(self.N)) return (numerator/denominator)**(1.0/(p-q)) else: numerator = sum(log(self.di_power(i=i, power=1))*self.di_power(i=i, power=p)*self.number_fractions[i] for i in range(self.N)) denominator = sum(self.di_power(i=i, power=q)*self.number_fractions[i] for i in range(self.N)) return exp(numerator/denominator)
[docs] def mean_size_ISO(self, k, r): r''' >>> import numpy as np >>> ds = 1E-6*np.array([240, 360, 450, 562.5, 703, 878, 1097, 1371, 1713, 2141, 2676, 3345, 4181, 5226, 6532]) >>> numbers = [65, 119, 232, 410, 629, 849, 990, 981, 825, 579, 297, 111, 21, 1] >>> psd = ParticleSizeDistribution(ds=ds, fractions=numbers, order=0) >>> psd.mean_size_ISO(1, 2) 0.002269321031745045 ''' p = k + r q = r return self.mean_size(p=p, q=q)
@property def vssa(self): r'''The volume-specific surface area of a particle size distribution. Note this uses the diameters provided by the method `Dis`. .. math:: \text{VSSA} = \sum_i \text{fraction}_i \frac{SA_i}{V_i} Returns ------- VSSA : float The volume-specific surface area of the distribution, [m^2/m^3] References ---------- .. [1] ISO 9276-2:2014 - Representation of Results of Particle Size Analysis - Part 2: Calculation of Average Particle Sizes/Diameters and Moments from Particle Size Distributions. ''' ds = self.Dis Vs = [pi/6*di**3 for di in ds] SAs = [pi*di**2 for di in ds] SASs = [SA/V for SA, V in zip(SAs, Vs)] VSSA = sum([fi*SASi for fi, SASi in zip(self.fractions, SASs)]) return VSSA
try: # pragma: no cover # Python 2 ParticleSizeDistributionContinuous.mean_size.__func__.__doc__ = _mean_size_docstring %(ParticleSizeDistributionContinuous.mean_size.__func__.__doc__) ParticleSizeDistributionContinuous.mean_size_ISO.__func__.__doc__ = _mean_size_iso_docstring %(ParticleSizeDistributionContinuous.mean_size_ISO.__func__.__doc__) ParticleSizeDistribution.mean_size.__func__.__doc__ = _mean_size_docstring %(ParticleSizeDistribution.mean_size.__func__.__doc__) ParticleSizeDistribution.mean_size_ISO.__func__.__doc__ = _mean_size_iso_docstring %(ParticleSizeDistribution.mean_size_ISO.__func__.__doc__) except AttributeError: # pragma: no cover try: # Python 3 ParticleSizeDistributionContinuous.mean_size.__doc__ = _mean_size_docstring %(ParticleSizeDistributionContinuous.mean_size.__doc__) ParticleSizeDistributionContinuous.mean_size_ISO.__doc__ = _mean_size_iso_docstring %(ParticleSizeDistributionContinuous.mean_size_ISO.__doc__) ParticleSizeDistribution.mean_size.__doc__ = _mean_size_docstring %(ParticleSizeDistribution.mean_size.__doc__) ParticleSizeDistribution.mean_size_ISO.__doc__ = _mean_size_iso_docstring %(ParticleSizeDistribution.mean_size_ISO.__doc__) except: pass # micropython del _mean_size_iso_docstring del _mean_size_docstring
[docs]class PSDLognormal(ParticleSizeDistributionContinuous): name = 'Lognormal' points = False truncated = False def __init__(self, d_characteristic, s, order=3, d_min=None, d_max=None): self.s = s self.d_characteristic = d_characteristic self.order = order self.parameters = {'s': s, 'd_characteristic': d_characteristic, 'd_min': d_min, 'd_max': d_max} self.d_min = d_min self.d_max = d_max # Pick an upper bound for the search algorithm of 15 orders of magnitude larger than # the characteristic diameter; should never be a problem, as diameters can only range # so much, physically. if self.d_max is not None: self.d_excessive = self.d_max else: self.d_excessive = 1E15*self.d_characteristic if self.d_min is not None: self.d_minimum = self.d_min else: self.d_minimum = 0.0 if self.d_min is not None or self.d_max is not None: self.truncated = True if self.d_max is None: self.d_max = self.d_excessive if self.d_min is None: self.d_min = 0.0 self._cdf_d_max = self._cdf(self.d_max) self._cdf_d_min = self._cdf(self.d_min) def _pdf(self, d): return pdf_lognormal(d, d_characteristic=self.d_characteristic, s=self.s) def _cdf(self, d): return cdf_lognormal(d, d_characteristic=self.d_characteristic, s=self.s) def _pdf_basis_integral(self, d, n): return pdf_lognormal_basis_integral(d, d_characteristic=self.d_characteristic, s=self.s, n=n)
[docs]class PSDGatesGaudinSchuhman(ParticleSizeDistributionContinuous): name = 'Gates Gaudin Schuhman' points = False truncated = False def __init__(self, d_characteristic, m, order=3, d_min=None, d_max=None): self.m = m self.d_characteristic = d_characteristic self.order = order self.parameters = {'m': m, 'd_characteristic': d_characteristic, 'd_min': d_min, 'd_max': d_max} if self.d_max is not None: # PDF above this is zero self.d_excessive = self.d_max else: self.d_excessive = self.d_characteristic if self.d_min is not None: self.d_minimum = self.d_min else: self.d_minimum = 0.0 if self.d_min is not None or self.d_max is not None: self.truncated = True if self.d_max is None: self.d_max = self.d_excessive if self.d_min is None: self.d_min = 0.0 self._cdf_d_max = self._cdf(self.d_max) self._cdf_d_min = self._cdf(self.d_min) def _pdf(self, d): return pdf_Gates_Gaudin_Schuhman(d, d_characteristic=self.d_characteristic, m=self.m) def _cdf(self, d): return cdf_Gates_Gaudin_Schuhman(d, d_characteristic=self.d_characteristic, m=self.m) def _pdf_basis_integral(self, d, n): return pdf_Gates_Gaudin_Schuhman_basis_integral(d, d_characteristic=self.d_characteristic, m=self.m, n=n)
[docs]class PSDRosinRammler(ParticleSizeDistributionContinuous): name = 'Rosin Rammler' points = False truncated = False def __init__(self, k, m, order=3, d_min=None, d_max=None): self.m = m self.k = k self.order = order self.parameters = {'m': m, 'k': k, 'd_min': d_min, 'd_max': d_max} if self.d_max is not None: self.d_excessive = self.d_max else: self.d_excessive = 1e15 if self.d_min is not None: self.d_minimum = self.d_min else: self.d_minimum = 0.0 if self.d_min is not None or self.d_max is not None: self.truncated = True if self.d_max is None: self.d_max = self.d_excessive if self.d_min is None: self.d_min = 0.0 self._cdf_d_max = self._cdf(self.d_max) self._cdf_d_min = self._cdf(self.d_min) def _pdf(self, d): return pdf_Rosin_Rammler(d, k=self.k, m=self.m) def _cdf(self, d): return cdf_Rosin_Rammler(d, k=self.k, m=self.m) def _pdf_basis_integral(self, d, n): return pdf_Rosin_Rammler_basis_integral(d, k=self.k, m=self.m, n=n)
"""# These are all brutally slow! from scipy.stats import * from fluids import * distribution = lognorm(s=0.5, scale=5E-6) psd = PSDCustom(distribution) # psd.dn(0.5, n=2.0) # Doesn't work at all, but the main things do including plots """
[docs]class PSDCustom(ParticleSizeDistributionContinuous): name = '' points = False truncated = False def __init__(self, distribution, order=3.0, d_excessive=1.0, name=None, d_min=None, d_max=None): if name: self.name = name else: try: self.name = distribution.dist.__class__.__name__ except: pass try: self.parameters = dict(distribution.kwds) self.parameters.update({'d_min': d_min, 'd_max': d_max}) except: self.parameters = {'d_min': d_min, 'd_max': d_max} self.distribution = distribution self.order = order self.d_max = d_max self.d_min = d_min if self.d_max is not None: self.d_excessive = self.d_max else: self.d_excessive = d_excessive if self.d_min is not None: self.d_minimum = self.d_min else: self.d_minimum = 0.0 if self.d_min is not None or self.d_max is not None: self.truncated = True if self.d_max is None: self.d_max = self.d_excessive if self.d_min is None: self.d_min = 0.0 self._cdf_d_max = self._cdf(self.d_max) self._cdf_d_min = self._cdf(self.d_min) def _pdf(self, d): return self.distribution.pdf(d) def _cdf(self, d): return self.distribution.cdf(d) def _pdf_basis_integral_definite(self, d_min, d_max, n): # Needed as an api for numerical integrals n = float(n) if d_min == 0: d_min = d_max*1E-12 if n == 0: to_int = lambda d : self._pdf(d) elif n == 1: to_int = lambda d : d*self._pdf(d) elif n == 2: to_int = lambda d : d*d*self._pdf(d) elif n == 3: to_int = lambda d : d*d*d*self._pdf(d) else: to_int = lambda d : d**n*self._pdf(d) # points = logspace(log10(max(d_max*1e-3, d_min)), log10(d_max*.999), 40) points = [d_max*1e-3] # d_min*.999 d_min return float(quad(to_int, d_min, d_max, points=points)[0]) #
[docs]class PSDInterpolated(ParticleSizeDistributionContinuous): name = 'Interpolated' points = True truncated = False def __init__(self, ds, fractions, order=3, monotonic=True): self.order = order self.monotonic = monotonic self.parameters = {} ds = list(ds) fractions = list(fractions) if len(ds) == len(fractions)+1: # size classes, the last point will be zero fractions.insert(0, 0.0) self.d_minimum = min(ds) elif ds[0] != 0: ds = [0] + ds if len(ds) != len(fractions): fractions = [0] + fractions self.d_minimum = 0.0 self.ds = ds self.fractions = fractions self.d_excessive = max(ds) self.fraction_cdf = cumsum(fractions) if self.monotonic: from scipy.interpolate import PchipInterpolator globals()['PchipInterpolator'] = PchipInterpolator self.cdf_spline = PchipInterpolator(ds, self.fraction_cdf, extrapolate=True) self.pdf_spline = PchipInterpolator(ds, self.fraction_cdf, extrapolate=True).derivative(1) else: from scipy.interpolate import UnivariateSpline globals()['UnivariateSpline'] = UnivariateSpline self.cdf_spline = UnivariateSpline(ds, self.fraction_cdf, ext=3, s=0) self.pdf_spline = UnivariateSpline(ds, self.fraction_cdf, ext=3, s=0).derivative(1) # The pdf basis integral splines will be stored here self.basis_integrals = {} def _pdf(self, d): return max(0.0, float(self.pdf_spline(d))) def _cdf(self, d): if d > self.d_excessive: # Handle spline values past 1 that decrease to zero return 1.0 return max(0.0, float(self.cdf_spline(d))) def _pdf_basis_integral(self, d, n): # there are slight errors with this approach - but they are OK to # ignore. # DO NOT evaluate the first point as it leads to inf values; just set # it to zero from fluids.numerics import numpy as np if n not in self.basis_integrals: ds = np.array(self.ds[1:]) pdf_vals = self.pdf_spline(ds) basis_integral = ds**n*pdf_vals if self.monotonic: from scipy.interpolate import PchipInterpolator self.basis_integrals[n] = PchipInterpolator(ds, basis_integral, extrapolate=True).antiderivative(1) else: from scipy.interpolate import UnivariateSpline self.basis_integrals[n] = UnivariateSpline(ds, basis_integral, ext=3, s=0).antiderivative(n=1) return max(float(self.basis_integrals[n](d)), 0.0)