Pore-Scale Models#

The pore-scale model is one of the most important parts of OpenPNM since this is how the geometrical and transport properties of each pore/throat are computed. The pore-scale model mechanism in OpenPNM was designed to make it easy for users to customize models or create their own. This notebook will cover the process in detail

import openpnm as op
import numpy as np
import matplotlib.pyplot as plt
op.visualization.set_mpl_style()
pn = op.network.Cubic([3, 3, 1], spacing=5e-5)

As we can see, the Cubic class has a few pore-scale models already attached to it, but none of them compute geometrical information.

print(pn.models)
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#   Property Name                       Parameter                 Value
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1   pore.coordination_number@all        model:                    coordination_number
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
2   throat.spacing@all                  model:                    pore_to_pore_distance
                                        regeneration mode:        deferred
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

In this notebook we’ll add pore-scale models for computing seed values to put into each pore, then finding the pore and throat diameter by using those seed values in statistical distributions. Let’s start by creating our own model to compute pore seed values. This model needs to receive the network so that is knows how many pores require values. We might also want to limit the range of values it returns to avoid getting size values in the long tails of the distribution. And finally we might want to set the random number generator’s starting point. These features are all included in the following function definition:

def pore_seed(network, num_range=[0.1, 0.9], seed=None):
    Np = network.Np
    np.random.seed(seed)
    vals = np.random.rand(Np)
    vals = vals*(num_range[1] - num_range[0]) + num_range[0]
    return vals

Now let’s call our function to see how it works:

vals = pore_seed(network=pn, num_range=[0.2, 0.8], seed=0)
print(vals)
[0.5292881  0.62911362 0.56165803 0.52692991 0.45419288 0.58753647
 0.46255233 0.7350638  0.77819766]

You might be wondering why we both to return the values instead of just writing them to the network which we happen to have direct access to since it was passed in as an argument. The reason is that we don’t necessarily know what dictionary key the user wants these values stored in. It’s likely to be 'pore.seed', but we don’t want to force this on a user. Instead, the model is expect to return the values, which are then caught and written to the network object:

pn['pore.seed'] = pore_seed(network=pn, num_range=[0.2, 0.8], seed=0)
print(pn['pore.seed'])
[0.5292881  0.62911362 0.56165803 0.52692991 0.45419288 0.58753647
 0.46255233 0.7350638  0.77819766]

The next feature we might like is the ability to re-run the models. So instead of having to re-write the above line at various placesn throughout our script, it is desirable to store the model on the object for later use. For this purpose there is a models attribute on every OpenPNM object. The models attribute is actually a dictionary where the keys indicate where the proudced data should be stored.

pn.models['pore.diameter@all'] = {'model': pore_seed, 
                                  'num_range': [0.2, 0.8],
                                  'seed': None}