Creating a Custom Phase#

Creating a custom fluid using GenericPhase#

OpenPNM comes with a small selection of pre-written phases (Air, Water, Mercury). In many cases users will want different options but it is not feasible or productive to include a wide variety of fluids. Consequntly OpenPNM has a mechanism for creating custom phases for this scneario. This requires that the user have correlations for the properties of interest, such as the viscosity as a function of temperature in the form of a polynomial for instance. This is process is described in the following tutuorial:

Import the usual packages and instantiate a small network for demonstration purposes:

[1]:
import numpy as np
import openpnm as op
[2]:
pn = op.network.Cubic(shape=[3, 3, 3], spacing=1e-4)
print(pn)
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
openpnm.network.Cubic : net_01
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#     Properties                                    Valid Values
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1     pore.coords                                      27 / 27
2     throat.conns                                     54 / 54
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#     Labels                                        Assigned Locations
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1     pore.all                                      27
2     pore.back                                     9
3     pore.bottom                                   9
4     pore.front                                    9
5     pore.internal                                 27
6     pore.left                                     9
7     pore.right                                    9
8     pore.surface                                  26
9     pore.top                                      9
10    throat.all                                    54
11    throat.internal                               54
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Parameters                          Values
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

Now that a network is defined, we can create a GenericPhase object associated with it. For this demo we’ll make an oil phase, so let’s call it oil:

[3]:
oil = op.phases.GenericPhase(network=pn)
print(oil)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [3], in <cell line: 1>()
----> 1 oil = op.phases.GenericPhase(network=pn)
      2 print(oil)

AttributeError: module 'openpnm' has no attribute 'phases'

As can be seen in the above printout, this phase has a temperature and pressure set at all locations, but has no other physical properties.

There are 2 ways add physical properties. They can be hard-coded, or added as a ‘pore-scale model’.
- Some are suitable as hard coded values, such as molecular mass - Others should be added as a model, such as viscosity, which is a function of temperature so could vary spatially and should be updated depending on changing conditions in the simulation.

Start with hard-coding:

[4]:
oil['pore.molecular_mass'] = 100.0  # g/mol
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 oil['pore.molecular_mass'] = 100.0

NameError: name 'oil' is not defined
[5]:
print(oil['pore.molecular_mass'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 print(oil['pore.molecular_mass'])

NameError: name 'oil' is not defined

As can be seen, this puts the value of 100.0 g/mol in every pore. Note that you could also assign each pore explicitly with a numpy array. OpenPNM automatically assigns a scalar value to every location as shown above.

[6]:
oil['pore.molecular_mass'] = np.ones(shape=[pn.Np, ])*120.0
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 oil['pore.molecular_mass'] = np.ones(shape=[pn.Np, ])*120.0

NameError: name 'oil' is not defined
[7]:
print(oil['pore.molecular_mass'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [7], in <cell line: 1>()
----> 1 print(oil['pore.molecular_mass'])

NameError: name 'oil' is not defined

You can also specify something like viscosity this way as well, but it’s not recommended:

[8]:
oil['pore.viscosity'] = 1600.0  # cP
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 oil['pore.viscosity'] = 1600.0

NameError: name 'oil' is not defined

The problem with specifying the viscosity as a hard-coded value is that viscosity is a function of temperature (among other things), so if we adjust the temperature on the oil object it will have no effect on the hard-coded viscosity:

[9]:
oil['pore.temperature'] = 100.0  # C
print(oil['pore.viscosity'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 oil['pore.temperature'] = 100.0  # C
      2 print(oil['pore.viscosity'])

NameError: name 'oil' is not defined

The correct way to specify something like viscosity is to use pore-scale models. There is a large libary of pre-written models in the openpnm.models submodule. For instance, a polynomial can be used as follows:

\[viscosity = a_0 + a_1 \cdot T + a_2 \cdot T^2 = 1600 + 12 T - 0.05 T^2\]
[10]:
mod = op.models.misc.polynomial
oil.add_model(propname='pore.viscosity', model=mod,
            a=[1600, 12, -0.05], prop='pore.temperature')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [10], in <cell line: 2>()
      1 mod = op.models.misc.polynomial
----> 2 oil.add_model(propname='pore.viscosity', model=mod,
      3             a=[1600, 12, -0.05], prop='pore.temperature')

NameError: name 'oil' is not defined

We can now see that our previously written values of viscosity (1600.0) have been overwritten by the values coming from the model:

[11]:
print(oil['pore.viscosity'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [11], in <cell line: 1>()
----> 1 print(oil['pore.viscosity'])

NameError: name 'oil' is not defined

And moreover, if we change the temperature the model will update the viscosity values:

[12]:
oil['pore.temperature'] = 40.0  # C
oil.regenerate_models()
print(oil['pore.viscosity'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [12], in <cell line: 1>()
----> 1 oil['pore.temperature'] = 40.0  # C
      2 oil.regenerate_models()
      3 print(oil['pore.viscosity'])

NameError: name 'oil' is not defined

Note the call to regenerate_models, which is necessary to actually re-run the model using the new temperature.

When a pore-scale model is added to an object, it is stored under the models attribute, which is a dictionary with names corresponding the property that is being calculated (i.e. ‘pore.viscosity’):

[13]:
print(oil.models)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [13], in <cell line: 1>()
----> 1 print(oil.models)

NameError: name 'oil' is not defined

We can reach into this dictionary and alter the parameters of the model if necessary:

[14]:
oil.models['pore.viscosity']['a'] = [1200, 10, -0.02]
oil.regenerate_models()
print(oil['pore.viscosity'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [14], in <cell line: 1>()
----> 1 oil.models['pore.viscosity']['a'] = [1200, 10, -0.02]
      2 oil.regenerate_models()
      3 print(oil['pore.viscosity'])

NameError: name 'oil' is not defined

The models submodule has a variety of common functions, stored under models.misc. There are also some models specific to physical properties under models.phases.