The Stick and Ball Geometry#

The SpheresAndCylinders class contains an assortment of pore-scale models that generate geometrical information assuming the pores are spherical and throats are cylindrical.

The SpheresAndCylinders is a perfect starting point for generating your own custom geometry. In fact, it’s likely that only the calculation of ‘pore.diameter’ would need to be changed. By default the ‘pore.diameter’ values are drawn from a random distribution which is not very realistic. Luckily, it’s easy to update the model used to calculate diameter, and then propagate this change to all the dependent values (i.e. ‘pore.volume’), as illustrated below.

[1]:
import openpnm as op
%config InlineBackend.figure_formats = ['svg']
import matplotlib.pyplot as plt
[2]:
pn = op.network.Cubic(shape=[20, 20, 20], spacing=100)

The spacing of the above network is in um for this example to make values easier to read, but in general you should always use SI

Now we can create a geometry object based on the SpheresAndCylinders:

[3]:
geo = op.geometry.SpheresAndCylinders(network=pn, pores=pn.Ps, throats=pn.Ts)

As can be seen by printing it, there are quite a few geometrical properties already added to this object. Defining these manually would have been a pain, so it’s a good idea to start with this class then alter the few models that need it:

[4]:
print(geo)
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
openpnm.geometry.SpheresAndCylinders : geo_01
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#     Properties                                    Valid Values
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1     pore.diameter                                  8000 / 8000
2     pore.max_size                                  8000 / 8000
3     pore.seed                                      8000 / 8000
4     pore.volume                                    8000 / 8000
5     throat.cross_sectional_area                   22800 / 22800
6     throat.diameter                               22800 / 22800
7     throat.diffusive_size_factors.pore1           22800 / 22800
8     throat.diffusive_size_factors.pore2           22800 / 22800
9     throat.diffusive_size_factors.th...           22800 / 22800
10    throat.hydraulic_size_factors.pore1           22800 / 22800
11    throat.hydraulic_size_factors.pore2           22800 / 22800
12    throat.hydraulic_size_factors.th...           22800 / 22800
13    throat.length                                 22800 / 22800
14    throat.max_size                               22800 / 22800
15    throat.volume                                 22800 / 22800
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Parameters                          Values
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

The pore size distribution on the SpheresAndCylinders is probably the more likely thing to change, since it is a random (i.e. uniform distribution) as shown below:

[5]:
fig = plt.hist(geo['pore.diameter'], bins=25, edgecolor='k')
../../../_images/examples_tutorials_geometry_stick_and_ball_9_0.svg

The models on the geo object can be seen by printing them:

[6]:
print(geo.models)
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#   Property Name                       Parameter                 Value
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1   pore.seed                           model:                    random
                                        element:                  pore
                                        num_range:                [0.2, 0.7]
                                        seed:                     None
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
2   pore.max_size                       model:                    largest_sphere
                                        iters:                    10
                                        fixed_diameter:           pore.fixed_diameter
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
3   pore.diameter                       model:                    product
                                        props:                    ['pore.max_size', 'pore.seed']
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
4   pore.volume                         model:                    sphere
                                        pore_diameter:            pore.diameter
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
5   throat.max_size                     model:                    from_neighbor_pores
                                        mode:                     min
                                        prop:                     pore.diameter
                                        ignore_nans:              True
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
6   throat.diameter                     model:                    scaled
                                        factor:                   0.5
                                        prop:                     throat.max_size
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
7   throat.length                       model:                    spheres_and_cylinders
                                        pore_diameter:            pore.diameter
                                        throat_diameter:          throat.diameter
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
8   throat.cross_sectional_area         model:                    cylinder
                                        throat_diameter:          throat.diameter
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
9   throat.volume                       model:                    cylinder
                                        throat_diameter:          throat.diameter
                                        throat_length:            throat.length
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
10  throat.diffusive_size_factors       model:                    spheres_and_cylinders
                                        pore_diameter:            pore.diameter
                                        throat_diameter:          throat.diameter
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
11  throat.hydraulic_size_factors       model:                    spheres_and_cylinders
                                        pore_diameter:            pore.diameter
                                        throat_diameter:          throat.diameter
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

In this tutorial we will change how pore sizes are calculated. We can do this by assigning a new pore-scale model for ‘pore.diameter’. Let’s use Gaussian distribution:

[7]:
f = op.models.geometry.pore_size.normal
geo.add_model(propname='pore.diameter',
              model=f,
              loc=50, scale=10)

This model is automatically run when it’s assigned, so we can inspect the new pore diameter values:

[8]:
fig = plt.hist(geo['pore.diameter'], bins=25, edgecolor='k')
../../../_images/examples_tutorials_geometry_stick_and_ball_15_0.svg

The above distribution does not look very much like a Gaussian distribution. This is because the ‘pore.seed’ values are truncated between 0.2 and 0.7:

[9]:
print(geo.models['pore.seed'])
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Property Name             Parameter                 Value
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
pore.seed                 model:                    random
                          element:                  pore
                          num_range:                [0.2, 0.7]
                          seed:                     None
                          regeneration mode:        deferred
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

We should change this to a wider range to capture more pores on the “tails”, then call regenerate_models, which will not only regenerate the random numbers, but all the other properties that depend on it such as ‘pore.diameter’, ‘pore.volume’, and so on:

[10]:
geo.models['pore.seed']['num_range'] = [0.001, 0.999]
geo.regenerate_models()
fig = plt.hist(geo['pore.diameter'], bins=25, edgecolor='k')
../../../_images/examples_tutorials_geometry_stick_and_ball_19_0.svg

A detailed example of adjusting pore-size distributions is given here