Working with parameters

Goal

  • Read in parameters for pure substances and mixtures from json files, and

  • create parameters for pure substances and mixtures within Python.

  • For both regular parameters as well as via the homo-segmented group contribution method.

  • Learn how to access information stored in parameter objects.

Parameters and the equation of state

Before we can start to compute physical properties, two steps are needed:

  1. Build a parameter object.

  2. Instatiate the equation of state using the parameter object.

In principle, every implementation of an equation of state can manage parameters differently but typically the workflow is similar for each implementation.

We first generate the parameters object, Parameters, which we then use to generate the equation of state object using EquationOfState.pcsaft().


Read parameters from json file(s)

The easiest way to create the Parameters object is to read information from one or more json files.

  • To read information from a single file, use Parameters.from_json

  • To read information from multiple files, use Parameters.from_multiple_json

From a single file

Pure substance

Querying a substance from a file requires an identifier. This identifier can be one of Name, Cas, Inchi, IupacName, Formula, or Smiles with Name (common english name) being the default. We can change the identifier type usig the identifier_option argument with an IdentifierOption object. Given a list of identifiers and a path to the parameter file, we can conveniently generate our object.

[1]:
import feos

# path to parameter file for substances that are non-associating, i.e. defined by three parameters: m, sigma, and epsilon_k.
file_na = '../../../parameters/pcsaft/gross2001.json'

# a system containing a single substance, "methane", using "Name" as identifier (default)
parameters = feos.Parameters.from_json(['methane'], pure_path=file_na)
parameters
[1]:

component

molarweight

m

sigma

epsilon_k

methane

16.043

1.0

3.7039

150.03

[2]:
# a system containing a single substance, "methane", using "Smiles" ("C") as identifier
parameters = feos.Parameters.from_json(['C'], pure_path=file_na, identifier_option=feos.IdentifierOption.Smiles)
parameters
[2]:

component

molarweight

m

sigma

epsilon_k

methane

16.043

1.0

3.7039

150.03

Mixtures

Reading parameters for more than one substance from a single file is very straight forward: simply add more identifiers to the list. Note that the order in which which identifiers are provided is important. When computing vector valued properties, the order of the physical properties matches the order of the substances within the parameter object.

[3]:
# a system containing a ternary mixture
parameters = feos.Parameters.from_json(
    ['methane', 'hexane', 'dodecane'],
    pure_path=file_na
)
parameters
[3]:

component

molarweight

m

sigma

epsilon_k

methane

16.043

1.0

3.7039

150.03

hexane

86.177

3.0576

3.7983

236.77

dodecane

170.338

5.306

3.8959

249.21

From multiple files

There may be cases where we have to split our parameter information across different files. For example, the feos repository has parameters stored in different files where each file corresponds to the parameter’s original publication. Constructing the parameter object using multiple different json files is a bit more complicated. We can provide a list tuples, each of which contains the list of substances and the file where parameters are stored.

In the example below, we define a 4 component mixture from three input files:

  • methane is read from a file containing non-associating substance parameters.

  • parameters for 1-butanol and water are read from a file containing associating substances, and

  • acetone parameters are read from a file that contains substances modelled with dipolar interactions.

[4]:
# na = non-associating
# assoc = associating
file_na = '../../../parameters/pcsaft/gross2001.json'
file_assoc = '../../../parameters/pcsaft/gross2002.json'
file_dipolar = '../../../parameters/pcsaft/gross2006.json'

parameters = feos.Parameters.from_multiple_json(
    [
        (['C'], file_na),
        (['CCCCO', 'O'], file_assoc),
        (['CC(C)=O'], file_dipolar)
    ],
    identifier_option=feos.IdentifierOption.Smiles
)
parameters
[4]:

component

molarweight

m

sigma

epsilon_k

mu

na

nb

kappa_ab

epsilon_k_ab

methane

16.043

1.0

3.7039

150.03

1-butanol

74.123

2.7515

3.6139

259.59

1

1

0.006692

2544.6

water

18.015

1.0656

3.0007

366.51

1

1

0.034868

2500.7

acetone

58.08

2.7447

3.2742

232.99

2.88

With binary interaction parameters

Some mixtures cannot be adequately described with combination rules from pure substance parameters. In PC-SAFT, we can use a binary interaction parameter, k_ij, to enhance the description of mixture behavior. These interaction parameters can be supplied from a json file via the binary_path option.

This parameter is not shown in the default representation of the parameter object. You can access the matrix of k_ij via the getter, PcSaftParameters.k_ij.

[5]:
file_na = '../../../parameters/pcsaft/gross2001.json'
file_assoc = '../../../parameters/pcsaft/gross2002.json'
file_binary = '../../../parameters/pcsaft/gross2002_binary.json'

parameters = feos.Parameters.from_multiple_json(
    [
        (['CCCC'], file_na),
        (['CCCCO',], file_assoc)
    ],
    binary_path=file_binary,
    identifier_option=feos.IdentifierOption.Smiles
)
parameters
[5]:

component

molarweight

m

sigma

epsilon_k

na

nb

kappa_ab

epsilon_k_ab

butane

58.123

2.3316

3.7086

222.88

1-butanol

74.123

2.7515

3.6139

259.59

1

1

0.006692

2544.6

component 1

component 2

k_ij

butane

1-butanol

0.015


Building parameters in Python

Building Parameters in Python is a bit more involved since the Parameters object is built from multiple intermediate objects. We need

  • the Identifier object that stores information about how a substance can be identified,

  • the PureRecord object that bundles identifier and parameters together with the molar weight.

  • the BinaryRecord object that contains binary interaction parameters.

[6]:
identifier = feos.Identifier(
    cas='106-97-8',
    name='butane',
    iupac_name='butane',
    smiles='CCCC',
    inchi='InChI=1/C4H10/c1-3-4-2/h3-4H2,1-2H3',
    formula='C4H10'
)
identifier
[6]:
Identifier(cas=106-97-8, name=butane, iupac_name=butane, smiles=CCCC, inchi=InChI=1/C4H10/c1-3-4-2/h3-4H2,1-2H3, formula=C4H10)

The PureRecord contains the identifier, molar weight and model parameters for a pure substance. At the point of creation, it has no information about the equation of state for which the parameters will ultimately be used. Therefore, there cannot be any checks for the validity of the parameters. Those happen when the equation of state is instantiated using, e.g., EquationOfState.pcsaft()

For PC-SAFT mandatory parameters are

  • the number of segments, m, which is a dimensionless floating point number,

  • the Lennard-Jones structure parameter (diameter), sigma, in units of Angstrom, and

  • the Lennard-Jones energy parameter, epsilon_k, in units of Kelvin.

Optional parameters are

  • the dipole moment, mu, in units of Debye used to model dipolar substances,

  • the quadrupole moment, q, in units of Debye used to model quadrupolar substances,

  • parameters to model association:

    • kappa_ab, epsilon_k_ab, na, nb

  • and parameters for entropy scaling:

    • viscosity, diffusion, and thermal_conductivity

    • each of which is a list containing coefficients for the respective correlation functions.

[7]:
butane = feos.PureRecord(identifier, molarweight=58.123, m=2.3316, sigma=3.7086, epsilon_k=222.88)
butane
[7]:
PureRecord(identifier: {cas: 106-97-8, name: butane, iupac_name: butane, smiles: CCCC, inchi: InChI=1/C4H10/c1-3-4-2/h3-4H2, 1-2H3, formula: C4H10}, molarweight: 58.123, m: 2.3316, sigma: 3.7086, epsilon_k: 222.88)

Parameters for a single component

For a single substance, we can use the Parameters.new_pure constructor.

[8]:
parameters = feos.Parameters.new_pure(butane)
parameters
[8]:

component

molarweight

m

sigma

epsilon_k

butane

58.123

2.3316

3.7086

222.88

Parameters for binary mixtures

We can create another PureRecord for a second component. Then, the Parameters.new_binary constructor let’s us build the parameters. Optionally, we can also directly provide a k_ij value for this system. Here, we build a record for an associating substance.

[9]:
butan_1_ol = feos.PureRecord(
    identifier=feos.Identifier(
        cas='71-36-3',
        name='1-butanol',
        iupac_name='butan-1-ol',
        smiles='CCCCO',
        inchi='InChI=1/C4H10O/c1-2-3-4-5/h5H,2-4H2,1H3',
        formula='C4H10O'
    ),
    molarweight=74.123,
    m=2.7515,
    sigma=3.6139,
    epsilon_k=259.59,
    kappa_ab=0.006692,
    epsilon_k_ab=2544.6
)
[10]:
parameters = feos.Parameters.new_binary([butane, butan_1_ol], k_ij=0.015)
parameters
[10]:

component

molarweight

m

sigma

epsilon_k

kappa_ab

epsilon_k_ab

butane

58.123

2.3316

3.7086

222.88

1-butanol

74.123

2.7515

3.6139

259.59

0.006692

2544.6

component 1

component 2

k_ij

butane

1-butanol

0.015

Parameters for mixtures with more than two components

For mixtures with more than two components, we can use the Parameters.from_records constructor which takes a list of PureRecords and optionally binary interaction parameters.

[11]:
binary = feos.BinaryRecord(feos.Identifier(name='butane'), feos.Identifier(name='1-butanol'), k_ij=0.015)

parameters = feos.Parameters.from_records(
    [butane, butan_1_ol],
    binary_records=[binary]
)
parameters
[11]:

component

molarweight

m

sigma

epsilon_k

kappa_ab

epsilon_k_ab

butane

58.123

2.3316

3.7086

222.88

1-butanol

74.123

2.7515

3.6139

259.59

0.006692

2544.6

component 1

component 2

k_ij

butane

1-butanol

0.015


Parameters from group contribution methods

An alternative to substance specific parameters are parameters that combine information from functional groups (molecule segments). As with regular parameters objects, we can build a GcParameters object from json or from Python - using segment information.

From json files

We need at least two files:

  • pure_path: a file containing the substance identifiers and the segments that form the molecule

  • segments_path: a file that contains the segments (identifier and model parameters)

As before, we can specify our substance identifier using identifier_option and we can provide binary interaction parameters (segment-segment k_ij) via the binary_path argument.

[12]:
pure_path = '../../../parameters/pcsaft/gc_substances.json'
segments_path = '../../../parameters/pcsaft/sauer2014_homo.json'

parameters = feos.GcParameters.from_json_segments(
    ['CCCC', 'CCCCO'],
    pure_path,
    segments_path,
    identifier_option=feos.IdentifierOption.Smiles
)
parameters
[12]:
<GcParameters at 0x7fbbc8311d70>

From Python

Building parameters in Python follows a similar approach as for regular parameters. To build GcParameters from segments, we need to specify:

  • The ChemicalRecord which contains the Identifier and the segments (as list of strs),

  • and the SegmentRecord which specifies the identifier of the segment (has to be the same as in the list of the ChemicalRecord), the molar weight and model parameters.

If both are available, we can use the GcParameters.from_segments constructor to build the parameters.

[13]:
cr1 = feos.ChemicalRecord(
    identifier=feos.Identifier(
    cas='106-97-8',
    name='butane',
    iupac_name='butane',
    smiles='CCCC',
    inchi='InChI=1/C4H10/c1-3-4-2/h3-4H2,1-2H3',
    formula='C4H10'
),
    segments=['CH3', 'CH2', 'CH2', 'CH3']
)

cr2 = feos.ChemicalRecord(
    identifier=feos.Identifier(
        cas='71-36-3',
        name='1-butanol',
        iupac_name='butan-1-ol',
        smiles='CCCCO',
        inchi='InChI=1/C4H10O/c1-2-3-4-5/h5H,2-4H2,1H3',
        formula='C4H10O'
    ),
    segments=['CH3', 'CH2', 'CH2', 'CH2', 'OH']
)

Each segment has a SegmentRecord which can be constructed just like we did before for a substance.

[14]:
ch3 = feos.SegmentRecord(
    'CH3',
    molarweight=15.0345,
    m=0.61198,
    sigma=3.7202,
    epsilon_k=229.90
)
ch2 = feos.SegmentRecord(
    'CH2',
    molarweight=14.02658,
    m=0.45606,
    sigma=3.8900,
    epsilon_k=239.01
)
oh = feos.SegmentRecord(
    'OH',
    molarweight=17.00734,
    m=0.40200,
    sigma=3.2859,
    epsilon_k=488.66,
    epsilon_k_ab=2517.0,
    kappa_ab=0.006825
)
[15]:
parameters = feos.GcParameters.from_segments(
    chemical_records=[cr1, cr2],
    segment_records=[ch3, ch2, oh]
)
parameters
[15]:
<GcParameters at 0x7fbbc8311590>

Accessing information from parameter objects

Once the Parameters object is constructed, within a jupyter notebook, we get a nice representation in form of a markdown table. Sometimes, however you might want to access information not presented in this table or you might want to store information in a variable.

Let’s build parameters for the four-component mixture we looked at earlier:

[16]:
file_na = '../../../parameters/pcsaft/gross2001.json'
file_assoc = '../../../parameters/pcsaft/gross2002.json'
file_dipolar = '../../../parameters/pcsaft/gross2006.json'

parameters = feos.Parameters.from_multiple_json(
    [
        (['C'], file_na),
        (['CCCCO', 'O'], file_assoc),
        (['CC(C)=O'], file_dipolar)
    ],
    identifier_option=feos.IdentifierOption.Smiles
)
parameters
[16]:

component

molarweight

m

sigma

epsilon_k

mu

na

nb

kappa_ab

epsilon_k_ab

methane

16.043

1.0

3.7039

150.03

1-butanol

74.123

2.7515

3.6139

259.59

1

1

0.006692

2544.6

water

18.015

1.0656

3.0007

366.51

1

1

0.034868

2500.7

acetone

58.08

2.7447

3.2742

232.99

2.88

Get PureRecords via parameters.pure_records

We have seen above that it is possible to generate parameters in Python using intermediate objects, such as Identifier, PcSaftRecord and PureRecord. You can generate these objects for all substances via the pure_records method (getter). This getter returns PureRecord objects which can be further deconstructed to yield the Identifier and PcSaftRecord objects and the molar weight.

Note that the order in which substances are returned matches the order in which we specified the substances above.

[17]:
parameters.pure_records
[17]:
[PureRecord(identifier: {cas: 74-82-8, name: methane, iupac_name: methane, smiles: C, inchi: InChI=1/CH4/h1H4, formula: CH4}, molarweight: 16.043, m: 1.0, sigma: 3.7039, epsilon_k: 150.03),
 PureRecord(identifier: {cas: 71-36-3, name: 1-butanol, iupac_name: butan-1-ol, smiles: CCCCO, inchi: InChI=1/C4H10O/c1-2-3-4-5/h5H, 2-4H2, 1H3, formula: C4H10O}, molarweight: 74.123, m: 2.7515, sigma: 3.6139, epsilon_k: 259.59, association_sites: [{kappa_ab: 0.006692, epsilon_k_ab: 2544.6, na: 1.0, nb: 1.0}]),
 PureRecord(identifier: {cas: 7732-18-5, name: water, iupac_name: oxidane, smiles: O, inchi: InChI=1/H2O/h1H2, formula: H2O}, molarweight: 18.015, m: 1.0656, sigma: 3.0007, epsilon_k: 366.51, association_sites: [{kappa_ab: 0.034868, epsilon_k_ab: 2500.7, na: 1.0, nb: 1.0}]),
 PureRecord(identifier: {cas: 67-64-1, name: acetone, iupac_name: propan-2-one, smiles: CC(C)=O, inchi: InChI=1/C3H6O/c1-3(2)4/h1-2H3, formula: C3H6O}, molarweight: 58.08, m: 2.7447, sigma: 3.2742, epsilon_k: 232.99, mu: 2.88)]
[18]:
for pure_record in parameters.pure_records:
    print(f"σ ({pure_record.identifier.name})\t = {pure_record.model_record["sigma"]} A")
σ (methane)      = 3.7039 A
σ (1-butanol)    = 3.6139 A
σ (water)        = 3.0007 A
σ (acetone)      = 3.2742 A
[19]:
# get identifier of substance 0
parameters.pure_records[0].identifier
[19]:
Identifier(cas=74-82-8, name=methane, iupac_name=methane, smiles=C, inchi=InChI=1/CH4/h1H4, formula=CH4)
[20]:
# get molarweight of substance 0
parameters.pure_records[0].molarweight
[20]:
16.043

A PureRecord object can be used to generate a json string which then can conveniently be stored in a file.

[21]:
# generate a json string from identifier of substance 0
parameters.pure_records[0].to_json_str()
[21]:
'{"identifier":{"cas":"74-82-8","name":"methane","iupac_name":"methane","smiles":"C","inchi":"InChI=1/CH4/h1H4","formula":"CH4"},"molarweight":16.043,"m":1.0,"sigma":3.7039,"epsilon_k":150.03}'

Accessing parameters from the equation of state

There is some parameter information that is not available from the Parameters struct:

  • The matrix of association parameters (because it depends on model-specific combining rules)

  • Parameters for homosegmented GC methods (because they depend on model-specific sum rules)

To access those and all other parameters, we need to build the equation of state first. Then we can access its parameters using the parameters attribute.

[22]:
eos = feos.EquationOfState.pcsaft(parameters)
eos.parameters
[22]:
{'m': array([1.    , 2.7515, 1.0656, 2.7447]),
 'sigma': array([3.7039, 3.6139, 3.0007, 3.2742]),
 'epsilon_k': array([150.03, 259.59, 366.51, 232.99]),
 'mu': array([0.  , 0.  , 0.  , 2.88]),
 'na': array([1., 1.]),
 'nb': array([1., 1.]),
 'kappa_ab': array([[0.006692  , 0.01527536],
        [0.01527536, 0.034868  ]]),
 'epsilon_k_ab': array([[2544.6 , 2522.65],
        [2522.65, 2500.7 ]])}

This also allows us to inspect parameters that are calculated from a homosegmented GC method

[23]:
parameters = feos.GcParameters.from_segments(
    chemical_records=[cr1, cr2],
    segment_records=[ch3, ch2, oh]
)
eos = feos.EquationOfState.pcsaft(parameters)
eos.parameters
[23]:
{'m': array([2.13608, 2.38216]),
 'sigma': array([3.79456883, 3.75681406]),
 'epsilon_k': array([233.79002903, 278.79916706]),
 'k_ij': array([[0., 0.],
        [0., 0.]])}

Summary

In \(\text{FeO}_\text{s}\), handling of parameters is not predefined within the underlying library. Each implementation of an equation of state has complete autonomy about the way parameters are handled. Howevery, we offer some convenient ways to automatize parameter handling which result in the methods presented in this example.

We looked at different ways to get parameters for the EquationOfState.pcsaft() equation of state, i.e.

  • how to read parameters for substances from one or more files where we use the json format to store information, and

  • how to generate parameters the same parameters in Python.

  • In a similar fashion, we showed how parameters can be assembled using a homo-GC method.

Once parameters are created, we can retrieve information by extracting the intermediate objects such as PureRecord, Identifier and PcSaftRecord.

Further reading

Concluding remkars

Hopefully you found this example helpful. If you have comments, critique or feedback, please let us know and consider opening an issue on github.