```python
# File Authorship Information
__author__ = """Matteo Lulli, Luca Biferale, Giacomo Falcucci,
                Mauro Sbragaglia and Xiaowen Shan"""
__copyright__ = """Copyright 2020-2021, Matteo Lulli, Luca Biferale,
Giacomo Falcucci, Mauro Sbragaglia and Xiaowen Shan, idea.deploy"""
__license__ = """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."""
__maintainer__ = "Matteo Lulli"
__email__ = "matteo.lulli@gmail.com"
__status__ = "Development"
```

```python
# Development cell
%load_ext autoreload
%autoreload 2
```

# Structure and Isotropy of Lattice Pressure Tensors for Multi-range Potentials

Authors: Matteo Lulli (1), Luca Biferale (2), Giacomo Falcucci (3), Mauro Sbragaglia (2) and Xiaowen Shan (1)

(1) Department of Mechanics and Aerospace Engineering, Southern University of Science and Technology, Shenzhen, Guangdong 518055, China

(2) Department of Physics \& INFN, University of Rome \"Tor Vergata\", Via della Ricerca Scientifica 1, 00133, Rome, Italy.

(3) Department of Enterprise Engineering \"Mario Lucertini\", University of Rome \"Tor Vergata\", Via del Politecnico 1, 00133 Rome, Italy; John A. Paulson School of Engineering and Applied Physics, *Harvard University*,  33 Oxford Street, 02138 Cambridge, Massachusetts, USA.

**Abstract:**
 We systematically analyze the tensorial structure of the lattice pressure tensors for a class of multiphase lattice Boltzmann models (LBM) with multi-range interactions. Due to lattice discrete effects, we show that the built-in isotropy properties of the lattice interaction forces are not necessarily mirrored in the corresponding lattice pressure tensor. We therefore outline a new procedure to retrieve the desired isotropy in the lattice pressure tensors via a suitable choice of multi-range potentials. The newly obtained LBM forcing schemes are tested via numerical simulations of non-ideal equilibrium interfaces and are shown to yield weaker and less spatially extended spurious currents with respect to previous higher order forcing schemes obtained by forcing isotropy requirements only.

The paper has been published on [https://journals.aps.org/pre/abstract/10.1103/PhysRevE.103.063309](https://journals.aps.org/pre/abstract/10.1103/PhysRevE.103.063309) and can also be retreived on the arXiv [https://arxiv.org/abs/2009.12522](https://arxiv.org/abs/2009.12522)

# Reproducibility

This document is intended for those interested readers who want to reproduce the results published on [https://journals.aps.org/pre/abstract/10.1103/PhysRevE.103.063309](https://journals.aps.org/pre/abstract/10.1103/PhysRevE.103.063309) and on the arXiv [https://arxiv.org/abs/2009.12522](https://arxiv.org/abs/2009.12522). In the present case the computational resources needed should be available in general.

Next development steps will include a class to measure the time required by each cell and output it in a .json file which can be sent to [matteo.lulli@gmail.com](mailto:matteo.lulli@gmail.com) so that average execution times will be availble and organized according to the hardware.

Each subsection can be executed independently and reproduce the results which will be stored locally in the directory 'reproduced-data', so that the data will be generated only once.

Plots can also be generated using the same scripts employed for the figures of the paper. Since there are some issues in executing these scripts in the Jupyter environment we include them as separated files which are called from the cells themselves. **In order to reproduce the plots a working 'latex' installation is necessary to be present on the system.**

This file will be kept updated for new local features and developments in the parent project [**idea.deploy**](https://github.com/lullimat/idea.deploy)

This file is supposed to be pulled from the repository [https://github.com/lullimat/arXiv-2009.12522](https://github.com/lullimat/arXiv-2009.12522), from within the "papers" directory the [**idea.deploy**](https://github.com/lullimat/idea.deploy) project.

## Details

- **The simulations cells for Figure 3 and Figure 4 generate ALL the data discussed in the paper**
- Each simulation cell can run independently on the other
- Only one architecture and device can be chosen in order to reproduce all the figures. This will become more general in a future release

# Paper Results
Using the next subsections/cells it is possible to reproduce all the results and plots reported in the paper.

## Content of Table I
In the next cell we compute the values displayed in Table I of the manuscript.

```python
# Folding Comment: click on the arrow to unfold the content of this cell
import sys
sys.path.append("../../")
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from IPython.display import display

from idpy.LBM.SCFStencils import SCFStencils, BasisVectors

def GetIsotropyCoefficients(stencil, weights_list = None):
    '''
    GetIsotropyCoefficients:
    function for printing the isotropy coefficients
    for a give stencil and an arbitrary set of weights
    compatibly with the stencil. If no set of weights
    is provided the solution already provided by the
    stencil class is used
    '''
    if weights_list is None:
        weights_list = stencil.w_sol[0]

    print("Isotropy Constants")
    # stencil.e_expr
    for elem in [2, 4]:
        _swap_sym = sp_symbols('e_{' + str(elem) + '}')
        display(_swap_sym)
        display(stencil.e_expr[elem])
        _value = stencil.e_expr[elem]

        for w_i in range(len(stencil.w_sym_list)):
            _value = _value.subs(stencil.w_sym_list[w_i],
                                 weights_list[w_i])
        display(_value)
        print()

def GetIsotropyConditions(stencil, order, weights_list = None):
    '''
    GetIsotropyConditions:
    function for printing the isotropy conditions
    for a given stencil and an arbitrary set of weights
    compatibly with the stencil. If no set of weights
    is provided the solution already provided by the
    stencil class is used
    '''
    if weights_list is None:
        weights_list = stencil.w_sol[0]

    print("Isotropy Conditions")

    _swap_sym = sp_symbols('I_{' + str(order) + '\,0}')
    display(_swap_sym)
    display(stencil.B2n_expr[order][0])
    _value = stencil.B2n_expr[order][0]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    display(_value)
    print()

    for i in range(len(stencil.B2q_expr[order])):
        _swap_sym = sp_symbols('I_{' + str(order) + '\,' + str(i + 1) + '}')
        display(_swap_sym)
        display(stencil.B2q_expr[order][i])
        _value = stencil.B2q_expr[order][i]
        for w_i in range(len(stencil.w_sym_list)):
            _value = _value.subs(stencil.w_sym_list[w_i],
                                 weights_list[w_i])
        display(_value)
        print()

def GetVarepsilon(stencil):
    '''
    GetVarepsilon:
    function for printing the value of \varepsilon
    the 5 weights expression is assumed
    '''
    w1, w2, w4, w5, w8, w9, w10 = sp_symbols("w(1) w(2) w(4) w(5) w(8) w(9) w(10)")
    w13, w16, w17 = sp_symbols("w(13) w(16) w(17)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8, w9, w10, w13, w16, w17]

    eps_expr = \
        (48*w4 + 96*w5 + 96*w8 + 288*w9 + 576*w10 + 704*w13 + 960*w16 + 1920*w17)/\
        (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8 + 342*w9 +
         696*w10 + 812*w13 + 1056*w16 + 2124*w17)

    for i in range(10 - len(stencil.w_sol[0])):
        eps_expr = eps_expr.subs(w_sym_list[len(stencil.w_sol[0]) + i], 0)

    display(eps)
    display(eps_expr)

    weights_list = None
    if len(stencil.w_sol[0]) != 10:
        len_diff = 10 - len(stencil.w_sol[0])
        if len_diff < 0:
            raise Exception("The number of weights must be 7 at most!")
        weights_list = stencil.w_sol[0] + [0 for i in range(len_diff)]
    else:
        weights_list = stencil.w_sol[0]

    _value = eps_expr
    for w_i in range(len(w_sym_list)):
        _value = _value.subs(w_sym_list[w_i],
                             weights_list[w_i])
    display(_value)
    print()

def GetLambdaChiI(stencil):
    '''
    GetLambdaChiI:
    unction for printing the value of \chi_I, \Lambda_I
    the 5 weights expression is assumed
    '''
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    chi_i, lambda_i = sp_symbols('\\chi_I \\Lambda_I')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    lambda_i_expr = sp_Rational('1/2')*w1 - 2*w2 + 6*w4 - 24*w8 - 6*w5

    weights_list = None
    if len(stencil.w_sol[0]) != 5:
        len_diff = 5 - len(stencil.w_sol[0])
        if len_diff < 0:
            raise Exception("The number of weights must be 5 at most!")
        weights_list = stencil.w_sol[0] + [0 for i in range(len_diff)]
    else:
        weights_list = stencil.w_sol[0]

    display(lambda_i)
    display(lambda_i_expr)
    _value = lambda_i_expr
    for w_i in range(len(w_sym_list)):
        _value = _value.subs(w_sym_list[w_i],
                             weights_list[w_i])
    display(_value)
    print()

    display(chi_i)
    display(chi_i_expr)
    _value = chi_i_expr
    for w_i in range(len(w_sym_list)):
        _value = _value.subs(w_sym_list[w_i],
                             weights_list[w_i])
    display(_value)
    print()

'''
Getting usual weights
'''

if True:
    '''
    E^6_F2P6
    '''
    S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4])
    S5_E6_P2F6.GetWolfEqs()
    S5_E6_P2F6.GetTypEqs()
    S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

    '''
    E^8_F2P8
    '''
    S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])
    S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

    '''
    E^10_F2P10
    '''
    S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                               len_2s = [1, 2, 4, 5, 8, 9, 10])
    S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

    '''
    E^12_F2P12
    '''
    S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                               len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
    S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
Getting new weights: always up to w(8)
'''
if True:
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8]

    eps_expr = (48*w4 + 96*w5 + 96*w8)
    eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

    chi_i_expr = 2*w4 - 8*w8 - w5

    '''
    E^6_F4P6
    '''
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

    '''
    E^8_P4F6
    '''
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    cond_e4 = S5_E8_P4F6.e_expr[4] - sp_Rational('4/7')
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('10/31')
    cond_chi_i = chi_i_expr

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E8_P4F6.typ_eq_s[4][0],
                      S5_E8_P4F6.typ_eq_s[6][0]]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

    '''
    E^10_P4F6
    '''
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    cond_e4 = S5_E10_P4F6.e_expr[4] - sp_Rational('12/17')
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('38/89')
    cond_chi_i = chi_i_expr

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E10_P4F6.typ_eq_s[4][0],
                       S5_E10_P4F6.typ_eq_s[6][0]]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

    '''
    E^12_P4F6
    '''
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    cond_e4 = S5_E12_P4F6.e_expr[4] - sp_Rational(120, 143)
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    cond_chi_i = chi_i_expr

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E12_P4F6.typ_eq_s[4][0],
                       S5_E12_P4F6.typ_eq_s[6][0]]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

'''
Printing
'''

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")

E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

display(E6_P2F6_sym)
print("Weights: ", S5_E6_P2F6_W)
GetIsotropyCoefficients(S5_E8_P2F8, weights_list = S5_E6_P2F6_W + [0,0])
GetVarepsilon(S5_E6_P2F6)
GetLambdaChiI(S5_E6_P2F6)
GetIsotropyConditions(S5_E6_P2F6, order = 4)
GetIsotropyConditions(S5_E6_P2F6, order = 6)

print("-------------------------------------------------------------------------")

display(E6_P4F6_sym)
print("Weights: ", S5_E6_P4F6_W)
GetIsotropyCoefficients(S5_E6_P4F6)
GetVarepsilon(S5_E6_P4F6)
GetLambdaChiI(S5_E6_P4F6)
GetIsotropyConditions(S5_E6_P4F6, order = 4)
GetIsotropyConditions(S5_E6_P4F6, order = 6)

print("=========================================================================")

display(E8_P2F8_sym)
print("Weights: ", S5_E8_P2F8_W)
GetIsotropyCoefficients(S5_E8_P2F8)
GetVarepsilon(S5_E8_P2F8)
GetLambdaChiI(S5_E8_P2F8)
GetIsotropyConditions(S5_E8_P2F8, order = 4)
GetIsotropyConditions(S5_E8_P2F8, order = 6)

print("-------------------------------------------------------------------------")

display(E8_P4F6_sym)
print("Weights: ", S5_E8_P4F6_W)
GetIsotropyCoefficients(S5_E8_P4F6)
GetVarepsilon(S5_E8_P4F6)
GetLambdaChiI(S5_E8_P4F6)
GetIsotropyConditions(S5_E8_P4F6, order = 4)
GetIsotropyConditions(S5_E8_P4F6, order = 6)

print("=========================================================================")

display(E10_P2F10_sym)
print("Weights: ", S5_E10_P2F10_W)
GetIsotropyCoefficients(S5_E10_P2F10)
GetVarepsilon(S5_E10_P2F10)
GetIsotropyConditions(S5_E10_P2F10, order = 4)
GetIsotropyConditions(S5_E10_P2F10, order = 6)

print("-------------------------------------------------------------------------")

display(E10_P4F6_sym)
print("Weights: ", S5_E10_P4F6_W)
GetIsotropyCoefficients(S5_E10_P4F6)
GetVarepsilon(S5_E10_P4F6)
GetLambdaChiI(S5_E10_P4F6)
GetIsotropyConditions(S5_E10_P4F6, order = 4)
GetIsotropyConditions(S5_E10_P4F6, order = 6)

print("=========================================================================")

display(E12_P2F12_sym)
print("Weights: ", S5_E12_P2F12_W)
GetIsotropyCoefficients(S5_E12_P2F12)
GetVarepsilon(S5_E12_P2F12)
GetIsotropyConditions(S5_E12_P2F12, order = 4)
GetIsotropyConditions(S5_E12_P2F12, order = 6)

print("-------------------------------------------------------------------------")

display(E12_P4F6_sym)
print("Weights: ", S5_E12_P4F6_W)
GetIsotropyCoefficients(S5_E12_P4F6)
GetVarepsilon(S5_E12_P4F6)
GetLambdaChiI(S5_E12_P4F6)
GetIsotropyConditions(S5_E12_P4F6, order = 4)
GetIsotropyConditions(S5_E12_P4F6, order = 6)
```

## Content of Table II
In the next cell we compute the values displayed in Table II of the manuscript.

```python
# Folding Comment: click on the arrow to unfold the content of this cell
import sys
sys.path.append("../../")
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from IPython.display import display

from idpy.LBM.SCFStencils import SCFStencils, BasisVectors

w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
eps = sp_symbols('\\varepsilon')
w_sym_list = [w1, w2, w4, w5, w8]

eps_expr = (48*w4 + 96*w5 + 96*w8)
eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

chi_i_expr = 2*w4 - 8*w8 - w5

chi_i, lambda_i = sp_symbols('\\chi_I \\Lambda_I')
I40, I60 = sp_symbols('I_{4\,0} I_{6\,0}')

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")

E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

'''
Getting usual weights
'''

'''
E^6_F2P6
'''
S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                         len_2s = [1, 2, 4])
S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

'''
E^8_F2P8
'''
S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                         len_2s = [1, 2, 4, 5, 8])
S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

'''
E^10_F2P10
'''
S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                           len_2s = [1, 2, 4, 5, 8, 9, 10])
S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

'''
E^12_F2P12
'''
S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                           len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
first: E6_P2F6
'''

display(E6_P2F6_sym)
print("Weights: ", S5_E6_P2F6_W)
print()

print("-------------------------------------------------------------------------")

'''
second: E6_P4F6
'''
if True:
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    print("System of equations:")
    display(S5_E6_P4F6.e_sym[2])
    display(cond_e2)
    print()
    display(eps)
    display(cond_eps)
    print()
    display(chi_i)
    display(cond_chi_i)
    print()
    display(I40)
    display(S5_E6_P4F6.typ_eq_s[4][0])
    print()
    display(I60)
    display(S5_E6_P4F6.typ_eq_s[6][0])
    print()

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

display(E6_P4F6_sym)
print("Weights: ", S5_E6_P4F6_W)

print("=========================================================================")

'''
third: E8_P2F8
'''

display(E8_P2F8_sym)
print("Weights: ", S5_E8_P2F8_W)
print()

print("-------------------------------------------------------------------------")

'''
fourth: E8_P4F6
'''
if True:
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    print("System of equations:")
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    display(S5_E8_P4F6.e_sym[2])
    display(cond_e2)
    print()
    cond_eps = eps_expr - sp_Rational('10/31')
    display(eps)
    display(cond_eps)
    print()
    cond_chi_i = chi_i_expr
    display(chi_i)
    display(cond_chi_i)
    print()
    cond_I40 = S5_E8_P4F6.typ_eq_s[4][0]
    display(I40)
    display(cond_I40)
    print()
    cond_I60 = S5_E8_P4F6.typ_eq_s[6][0]
    display(I60)
    display(cond_I60)
    print()

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      cond_I40,
                      cond_I60]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

display(E8_P4F6_sym)
print("Weights: ", S5_E8_P4F6_W)

print("=========================================================================")

'''
fifth: E10_P2F10
'''

display(E10_P2F10_sym)
print("Weights: ", S5_E10_P2F10_W)
print()

print("-------------------------------------------------------------------------")

'''
sixth: E10_P4F6
'''
if True:
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    print("System of equations:")
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    display(S5_E10_P4F6.e_sym[2])
    display(cond_e2)
    print()
    cond_eps = eps_expr - sp_Rational('38/89')
    display(eps)
    display(cond_eps)
    print()
    cond_chi_i = chi_i_expr
    display(chi_i)
    display(cond_chi_i)
    print()
    cond_I40 = S5_E10_P4F6.typ_eq_s[4][0]
    display(I40)
    display(cond_I40)
    print()
    cond_I60 = S5_E10_P4F6.typ_eq_s[6][0]
    display(I60)
    display(cond_I60)
    print()

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       cond_I40,
                       cond_I60]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

display(E10_P4F6_sym)
print("Weights: ", S5_E10_P4F6_W)

print("=========================================================================")

'''
seventh: E12_P2F12
'''

display(E12_P2F12_sym)
print("Weights: ", S5_E12_P2F12_W)
print()

print("-------------------------------------------------------------------------")

'''
eighth: E12_P4F6
'''
if True:
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    print("System of equations:")
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    display(S5_E12_P4F6.e_sym[2])
    display(cond_e2)
    print()
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    display(eps)
    display(cond_eps)
    print()
    cond_chi_i = chi_i_expr
    display(chi_i)
    display(cond_chi_i)
    print()
    cond_I40 = S5_E12_P4F6.typ_eq_s[4][0]
    display(I40)
    display(cond_I40)
    print()
    cond_I60 = S5_E12_P4F6.typ_eq_s[6][0]
    display(I60)
    display(cond_I60)
    print()

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       cond_I40,
                       cond_I60]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

display(E12_P4F6_sym)
print("Weights: ", S5_E12_P4F6_W)

print("=========================================================================")
```

## Simulations: Hardware Selection
In the following cells you are required to make a choice for the hardware to employ for the simulations. At the moment you need to stick to a given choice in order to visualize all the results. A more elastic management will be provided in future releases.

**These are the only cells in the notebook that have a "global" character, i.e. they are needed by the rest of the notebook for the simulations cell to run.**

By executing (shift-enter) the cell below, the hardware present in your system will be listed

```python
# Display Hardware (click the arrow to unfold)
import sys
sys.path.append("../../")

from idpy.IdpyCode import IdpyHardware

IdpyHardware()
```

**To repdroduce the results you need hardware that support OpenCL with 64bits floating point variables (indicated by 'Double :  63') and/or CUDA.**

The variable "preferred_lang" can be set as
- OCL_T for OpenCL
- CUDA_T for CUDA

The variable "preferred_device" need to be set to the number matching the 64-bits floating point precision requirement discussed above.

The variable "preferred_kind" can be used only with OCL_T and can be set as
- "gpu" for selecting GPU devices
- "cpu" for selecting CPU devices

It also possible to run the simulations on 32-bits floating point variables, however the results will not match those presented in the paper.

```python
# Language imports (click on left arrow to unfold)
import sys
sys.path.append("../../")

from idpy.IdpyCode import CUDA_T, OCL_T, idpy_langs_sys
```

```python
preferred_lang, preferred_device, preferred_kind = OCL_T, 0, "cpu"
```

```python
# Setting up global variables (click on left arrow to unfold)
import sys
sys.path.append("../../")

from idpy.IdpyCode import CUDA_T, OCL_T, idpy_langs_sys

device = None
if preferred_device is None:
    device = 0
else:
    device = preferred_device

lang = None
if preferred_lang is None:
    if idpy_langs_sys[CUDA_T]:
        lang = CUDA_T
    elif idpy_langs_sys[OCL_T]:
        lang = OCL_T
else:
    lang = preferred_lang

if lang is None:
    raise Exception("It seems idpy is not correctly initialized",
                    "Did you source the virtual environment before opening the notebook?",
                    "(idpy-load; ipdy-jupyter; then follow the instructions)")

dev_kind = None
if preferred_kind is None:
    dev_kind = "gpu"
else:
    dev_kind = preferred_kind

device_str = None
if lang == OCL_T:
    from idpy.OpenCL.OpenCL import OpenCL
    ocl = OpenCL()
    gpus_list = ocl.DiscoverGPUs()
    cpus_list = ocl.DiscoverCPUs()
    if dev_kind == "cpu":
        device_str = str(cpus_list[device]['Name']) + "_" + str(cpus_list[device]['DrvVersion'])
        device_str = device_str.replace(" ", "_").replace("(", "_").replace(")", "_")
        device_str = device_str.replace(",", "_").replace(".", "_").replace(":", "_")
        device_str = device_str.replace("@", "at")
        device_str = device_str.replace("{", "_").replace("}", "_")
    if dev_kind == "gpu":
        device_str = str(gpus_list[device]['Name']) + "_" + str(gpus_list[device]['DrvVersion'])
        device_str = device_str.replace(" ", "_").replace("(", "_").replace(")", "_")
        device_str = device_str.replace(",", "_").replace(".", "_").replace(":", "_")
        device_str = device_str.replace("@", "at")
        device_str = device_str.replace("{", "_").replace("}", "_")

if lang == CUDA_T:
    from idpy.CUDA.CUDA import CUDA
    cu = CUDA()
    gpus_list = cu.DiscoverGPUs()
    device_str = str(gpus_list[device]['Name']) + "_" + str(gpus_list[device]['DrvVersion'])
    device_str = device_str.replace(" ", "_").replace("(", "_").replace(")", "_")
    device_str = device_str.replace(",", "_").replace(".", "_").replace(":", "_")
    device_str = device_str.replace("@", "at")
    device_str = device_str.replace("{", "_").replace("}", "_")
```

## Figure 3

The simulation cell for this figure generates all the data needed for the curved interface results. For the flat interface use the (faster) simulation cells in Figure 4.

**Execution times for the cell below**

*Cached Equilibrium Densities*

**0 d,  7 h,  18 m,  33 s, CUDA, GeForce GTX 1070 (64-bits), Ryzen 5 3500X (6-Core) Processor**

*Uncached Equilibrium Densities*

**0 d,  7 h,  22 m,  32 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

```python
# Droplets Simulations for the Laplace data and velocity fields (click arrow to unfold)
import time
start = time.time()

import sys
sys.path.append("../../")

########################################################################

from pathlib import Path
reproduced_results = Path("reproduced-results")
if not reproduced_results.is_dir():
    reproduced_results.mkdir()

from sympy import exp as sp_exp
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from sympy import lambdify as sp_lambdify
from IPython.display import display
import numpy as np

from idpy.LBM.SCThermo import ShanChen
from idpy.Utils.ManageData import ManageData
from idpy.LBM.SCFStencils import SCFStencils, BasisVectors
from idpy.LBM.LBM import XIStencils

from idpy.LBM.SCThermo import ShanChanEquilibriumCache
from idpy.LBM.LBM import ShanChenMultiPhase, CheckUConvergence
from idpy.IdpyCode import IdpyMemory

def GetE2(stencil):
    weights_list = stencil.w_sol[0]
    _value = stencil.e_expr[2]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    return _value

'''
Here we declare the symbol for the density as 'n'
and define the pseudo-potential
'''

n = sp_symbols('n')
psis = [sp_exp(-1/n), 1 - sp_exp(-n)]
psi_codes = {psis[0]: 'exp((NType)(-1./ln))',
             psis[1]: '1. - exp(-(NType)ln)',}

Gs = {psis[0]: [-2.6, -3.1, -3.6],
      psis[1]: [-1.4, -1.6, -1.75]}

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")
E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

'''
Getting usual weights
'''

if True:
    '''
    E^6_F2P6
    '''
    S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4])
    S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

    '''
    E^8_F2P8
    '''
    S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])
    S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

    '''
    E^10_F2P10
    '''
    S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                               len_2s = [1, 2, 4, 5, 8, 9, 10])
    S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

    '''
    E^12_F2P12
    '''
    S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                               len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
    S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
Getting new weights: always up to w(8)
'''
if True:
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    eps_expr = (48*w4 + 96*w5 + 96*w8)
    eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

    '''
    E^6_F4P6
    '''
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

    '''
    E^8_P4F6
    '''
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    cond_e4 = S5_E8_P4F6.e_expr[4] - sp_Rational('4/7')
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('10/31')
    cond_chi_i = chi_i_expr

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E8_P4F6.typ_eq_s[4][0],
                      S5_E8_P4F6.typ_eq_s[6][0]]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

    '''
    E^10_P4F6
    '''
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    cond_e4 = S5_E10_P4F6.e_expr[4] - sp_Rational('12/17')
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('38/89')
    cond_chi_i = chi_i_expr

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E10_P4F6.typ_eq_s[4][0],
                       S5_E10_P4F6.typ_eq_s[6][0]]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

    '''
    E^12_P4F6
    '''
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    cond_e4 = S5_E12_P4F6.e_expr[4] - sp_Rational(120, 143)
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    cond_chi_i = chi_i_expr

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E12_P4F6.typ_eq_s[4][0],
                       S5_E12_P4F6.typ_eq_s[6][0]]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

'''
Defining output files: Flat interface
'''

stencil_string = {E6_P2F6_sym: 'E6_P2F6',
                  E6_P4F6_sym: 'E6_P4F6',
                  E8_P2F8_sym: 'E8_P2F8',
                  E8_P4F6_sym: 'E8_P4F6',
                  E10_P2F10_sym: 'E10_P2F10',
                  E10_P4F6_sym: 'E10_P4F6',
                  E12_P2F12_sym: 'E12_P2F12',
                  E12_P4F6_sym: 'E12_P4F6'}

stencil_dict = {E6_P2F6_sym: S5_E6_P2F6,
                E6_P4F6_sym: S5_E6_P4F6,
                E8_P2F8_sym: S5_E8_P2F8,
                E8_P4F6_sym: S5_E8_P4F6,
                E10_P2F10_sym: S5_E10_P2F10,
                E10_P4F6_sym: S5_E10_P4F6,
                E12_P2F12_sym: S5_E12_P2F12,
                E12_P4F6_sym: S5_E12_P4F6}

stencil_sym_list = [E6_P2F6_sym, E6_P4F6_sym,
                    E8_P2F8_sym, E8_P4F6_sym,
                    E10_P2F10_sym, E10_P4F6_sym,
                    E12_P2F12_sym, E12_P4F6_sym]

#stencil_sym_list = [E12_P2F12_sym, E12_P4F6_sym]

def LaplaceFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_laplace")

def DropletsFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_droplets")

def StencilPsiKey(stencil_sym, psi):
    return str(stencil_sym) + "_" + str(psi)

laplace_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[1])}

droplets_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[1])}

from idpy.Utils.ManageData import ManageData
'''
Simulations Loop
'''
for _psi in psis:
    print("The pseudo-potential is: ")
    display(_psi)
    for _G in Gs[_psi]:
        print("Coupling Constant G: ", _G)
        for stencil_sym in stencil_sym_list:
            display(stencil_sym)
            _stencil = stencil_dict[stencil_sym]
            _data_out_laplace = ManageData(dump_file = laplace_files[StencilPsiKey(stencil_sym, _psi)])
            _data_out_droplets = ManageData(dump_file = droplets_files[StencilPsiKey(stencil_sym, _psi)])

            _e2_swap = GetE2(_stencil)
            print("e2: ", _e2_swap)

            # [127, 159, 191, 223, 255, 287, 319, 351]
            for L in [127, 159, 191, 223, 255, 287, 319, 351]:
                print("L: ", L, "R: ", L/5.)

                _data_key = str(_G) + "_" + str(L)

                _is_data_there_laplace = False
                if _data_out_laplace.Read():
                    print("File ", laplace_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                    print("Checking if data is there...")
                    _is_data_there_laplace = _data_key in _data_out_laplace.WhichData()

                    if _is_data_there_laplace:
                        print("Data Found! No need to run the simulation")
                        print("To perform the simulation again remove the file:")
                        print(laplace_files[StencilPsiKey(stencil_sym, _psi)])
                        print()

                _is_data_there_droplets = False
                if L in [255, 351]:
                    if _data_out_droplets.Read():
                        print("File ", droplets_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                        print("Checking if data is there...")
                        _is_data_there_droplets = _data_key in _data_out_droplets.WhichData()

                        if _is_data_there_droplets:
                            print("Data Found! No need to run the simulation")
                            print("To perform the simulation again remove the file:")
                            print(droplets_files[StencilPsiKey(stencil_sym, _psi)])
                            print()
                else:
                    _is_data_there_droplets = True

                if not _is_data_there_laplace or not _is_data_there_droplets:
                    print("Data is not there...")
                    print("Preparing the simulation...")
                    print()
                    _lbm = ShanChenMultiPhase(lang = lang,
                                              dim_sizes = (L, L),
                                              xi_stencil = XIStencils['D2Q9'],
                                              f_stencil = _stencil.PushStencil(),
                                              psi_code = psi_codes[_psi],
                                              psi_sym = _psi,
                                              SC_G = _G, tau = 1.,
                                              device = device,
                                              cl_kind = dev_kind,
                                              optimizer_flag = True)

                    print("----------------------------------------------------")
                    print("\nComputing/Retrieving flat mechanical equilibrium densities...")
                    print("psi: ", _psi, "G: ", _G, "Ws: ", _stencil.w_sol[0])

                    _sc_eq_cache = ShanChanEquilibriumCache(stencil = _stencil,
                                                            psi_f = _psi, G = _G,
                                                            c2 = XIStencils['D2Q9']['c2'])

                    _eq_params = _sc_eq_cache.GetFromCache()
                    print("...done!\n")
                    print("The mechanical equilibrium densities are:")
                    print("n_g (gas): ", _eq_params['n_g'],
                          ", n_l (liq): ", _eq_params['n_l'])
                    print("Surface tension: ", _eq_params['sigma_f'])
                    print("(G_c, n_c) (crtitcal G and n): ", (_eq_params['G_c'],
                                                              _eq_params['n_c']))
                    print()
                    print("Initializing flat interface")

                    _lbm.InitRadialInterface(n_g = _eq_params['n_g'], n_l = _eq_params['n_l'],
                                             R = L/5., full_flag = True)

                    print("Running the simulation...")

                    _lbm.MainLoop(range(0, 2**22, 2**14),
                                  convergence_functions = [CheckUConvergence])

                    '''
                    Getting average density, inner and outer density for
                    computing the Gibbs radius
                    '''

                    _n_ave = IdpyMemory.Sum(_lbm.sims_idpy_memory['n'])/(L*L)
                    _n = _lbm.sims_idpy_memory['n'].D2H()
                    _n = _n.reshape(np.flip(_lbm.sims_vars['dim_sizes']))

                    _center = tuple(_lbm.sims_vars['dim_center'])
                    _n_in = _n[_center]
                    _n_out = _n[(L - 1, L - 1)]

                    _R_Gibbs = L*np.sqrt((_n_ave - _n_out)/(np.pi * (_n_in - _n_out)))

                    def BulkP(n_value, _e2):
                        _psi_f = sp_lambdify(n, _psi)
                        p = n_value * XIStencils['D2Q9']['c2']
                        p += 0.5 * _G * _e2 * (_psi_f(n_value)) ** 2
                        return p

                    _p_in, _p_out = BulkP(_n_in, _e2_swap), BulkP(_n_out, _e2_swap)
                    _delta_p = _p_in - _p_out

                    print("L: ", L, "Start R: ", L/5., "R_Gibbs: ", _R_Gibbs)
                    print("Estimated surface tension: ", _delta_p * _R_Gibbs)
                    print("Analytic surface tension: ", _eq_params['sigma_f'])

                    _dict_out = {'delta_p': _delta_p,
                                 'R_Gibbs': _R_Gibbs}

                    if not _is_data_there_laplace:
                        _data_out_laplace.PushData(data = _dict_out, key = _data_key)
                        _data_out_laplace.Dump()

                    if not _is_data_there_droplets:
                        _u = _lbm.sims_idpy_memory['u'].D2H()
                        _data_out_droplets.PushData(data = _u, key = _data_key)
                        _data_out_droplets.Dump()

                    print("Ending and deleting LB simulation object")
                    _lbm.End()
                    del _lbm
                    print()
                    print()

end = time.time()
def PrintElapsedTime(lapse):
    _n_sec_min, _n_min_hrs = 60, 60
    _n_sec_hrs, _n_hrs_day = _n_min_hrs * _n_sec_min, 24
    _n_sec_day = _n_hrs_day * _n_sec_hrs

    print(int(end - start)//_n_sec_day, "d, ",
          (int(end - start)//_n_sec_hrs)%_n_hrs_day, "h, ",
          (int(end - start)//_n_sec_min)%_n_min_hrs, "m, ",
          int(end - start)% _n_sec_min, "s")

PrintElapsedTime(start - end)
```

```python
# Figure 3 (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_3.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 3 (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_3.png")
```

## Figure 4

The simulation cell for this figure generates all the data needed for the flat interface results. For the curved interface use the (faster) simulation cells in Figure 3.

*Uncached Equilibrium Densities*

**0 d,  0 h,  27 m,  2 s, OpenCL, Apple M1 CPU (64-bits)**

**0 d,  0 h,  54 m,  36 s, CUDA, GeForce GTX 1070 (64-bits), Ryzen 5 3500X (6-Core) Processor**

**0 d,  0 h,  36 m,  25 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

*Cached Equilibrium Densities*

**0 d,  0 h,  8 m,  20 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

```python
# Flat Interfaces Simulations for the profiles comparison (click arrow to unfold)
import time

import sys
sys.path.append("../../")

########################################################################

from pathlib import Path
reproduced_results = Path("reproduced-results")
if not reproduced_results.is_dir():
    reproduced_results.mkdir()

from sympy import exp as sp_exp
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from IPython.display import display
import numpy as np

from idpy.LBM.SCThermo import ShanChen
from idpy.Utils.ManageData import ManageData
from idpy.LBM.SCFStencils import SCFStencils, BasisVectors
from idpy.LBM.LBM import XIStencils

from idpy.LBM.SCThermo import ShanChanEquilibriumCache
from idpy.LBM.LBM import ShanChenMultiPhase, CheckUConvergence

def GetE2(stencil):
    weights_list = stencil.w_sol[0]
    _value = stencil.e_expr[2]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    return _value

'''
Here we declare the symbol for the density as 'n'
and define the pseudo-potential
'''

n = sp_symbols('n')
psis = [sp_exp(-1/n), 1 - sp_exp(-n)]
psi_codes = {psis[0]: 'exp((NType)(-1./ln))',
             psis[1]: '1. - exp(-(NType)ln)',}

Gs = {psis[0]: [-2.6, -3.1, -3.6],
      psis[1]: [-1.4, -1.6, -1.75]}

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")
E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

'''
Getting usual weights
'''

if True:
    '''
    E^6_F2P6
    '''
    S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4])
    S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

    '''
    E^8_F2P8
    '''
    S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])
    S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

    '''
    E^10_F2P10
    '''
    S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                               len_2s = [1, 2, 4, 5, 8, 9, 10])
    S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

    '''
    E^12_F2P12
    '''
    S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                               len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
    S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
Getting new weights: always up to w(8)
'''
if True:
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    eps_expr = (48*w4 + 96*w5 + 96*w8)
    eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

    '''
    E^6_F4P6
    '''
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

    '''
    E^8_P4F6
    '''
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    cond_e4 = S5_E8_P4F6.e_expr[4] - sp_Rational('4/7')
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('10/31')
    cond_chi_i = chi_i_expr

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E8_P4F6.typ_eq_s[4][0],
                      S5_E8_P4F6.typ_eq_s[6][0]]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

    '''
    E^10_P4F6
    '''
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    cond_e4 = S5_E10_P4F6.e_expr[4] - sp_Rational('12/17')
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('38/89')
    cond_chi_i = chi_i_expr

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E10_P4F6.typ_eq_s[4][0],
                       S5_E10_P4F6.typ_eq_s[6][0]]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

    '''
    E^12_P4F6
    '''
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    cond_e4 = S5_E12_P4F6.e_expr[4] - sp_Rational(120, 143)
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    cond_chi_i = chi_i_expr

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E12_P4F6.typ_eq_s[4][0],
                       S5_E12_P4F6.typ_eq_s[6][0]]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

'''
Defining output files: Flat interface
'''

stencil_string = {E6_P2F6_sym: 'E6_P2F6',
                  E6_P4F6_sym: 'E6_P4F6',
                  E8_P2F8_sym: 'E8_P2F8',
                  E8_P4F6_sym: 'E8_P4F6',
                  E10_P2F10_sym: 'E10_P2F10',
                  E10_P4F6_sym: 'E10_P4F6',
                  E12_P2F12_sym: 'E12_P2F12',
                  E12_P4F6_sym: 'E12_P4F6'}

stencil_dict = {E6_P2F6_sym: S5_E6_P2F6,
                E6_P4F6_sym: S5_E6_P4F6,
                E8_P2F8_sym: S5_E8_P2F8,
                E8_P4F6_sym: S5_E8_P4F6,
                E10_P2F10_sym: S5_E10_P2F10,
                E10_P4F6_sym: S5_E10_P4F6,
                E12_P2F12_sym: S5_E12_P2F12,
                E12_P4F6_sym: S5_E12_P4F6}

stencil_sym_list = [E6_P2F6_sym, E6_P4F6_sym,
                    E8_P2F8_sym, E8_P4F6_sym,
                    E10_P2F10_sym, E10_P4F6_sym,
                    E12_P2F12_sym, E12_P4F6_sym]

def FlatFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_flat_profile")

def StencilPsiKey(stencil_sym, psi):
    return str(stencil_sym) + "_" + str(psi)

flat_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / FlatFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / FlatFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / FlatFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / FlatFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / FlatFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / FlatFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / FlatFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / FlatFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / FlatFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / FlatFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / FlatFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / FlatFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / FlatFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / FlatFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / FlatFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / FlatFileName(E12_P4F6_sym, psis[1])}

from idpy.Utils.ManageData import ManageData

'''
Simulations Loop
'''
start = time.time()

for _psi in psis:
    print("The pseudo-potential is: ")
    display(_psi)
    for _G in Gs[_psi]:
        print("Coupling Constant G: ", _G)
        for stencil_sym in stencil_sym_list:
            display(stencil_sym)
            _stencil = stencil_dict[stencil_sym]
            _data_out = ManageData(dump_file = flat_files[StencilPsiKey(stencil_sym, _psi)])

            _data_key = _G
            _e2_swap = GetE2(_stencil)

            _is_data_there = False
            if _data_out.Read():
                print("File ", flat_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                print("Checking if data is there...")
                _is_data_there = _data_key in _data_out.WhichData()

                if _is_data_there:
                    print("Data Found! No need to run the simulation")
                    print("To perform the simulation again remove the file:")
                    print(flat_files[StencilPsiKey(stencil_sym, _psi)])
                    print()

            if not _is_data_there:
                print("Data is not there...")
                print("Preparing the simulation...")
                print()
                _lbm = ShanChenMultiPhase(lang = lang,
                                          dim_sizes = (100, 4),
                                          xi_stencil = XIStencils['D2Q9'],
                                          f_stencil = _stencil.PushStencil(),
                                          psi_code = psi_codes[_psi],
                                          psi_sym = _psi,
                                          SC_G = _G, tau = 1.,
                                          device = device,
                                          cl_kind = dev_kind,
                                          optimizer_flag = False)

                print("----------------------------------------------------")
                print("\nComputing/Retrieving flat mechanical equilibrium densities...")
                print("psi: ", _psi, "G: ", _G, "Ws: ", _stencil.w_sol[0])

                _sc_eq_cache = ShanChanEquilibriumCache(stencil = _stencil,
                                                        psi_f = _psi, G = _G,
                                                        c2 = XIStencils['D2Q9']['c2'])

                _eq_params = _sc_eq_cache.GetFromCache()
                print("...done!\n")
                print("The mechanical equilibrium densities are:")
                print("n_g (gas): ", _eq_params['n_g'],
                      ", n_l (liq): ", _eq_params['n_l'])
                print("Surface tension: ", _eq_params['sigma_f'])
                print("(G_c, n_c) (crtitcal G and n): ", (_eq_params['G_c'],
                                                          _eq_params['n_c']))
                print()
                print("Initializing flat interface")

                '''
                _width: an arbitrary value
                '''
                _width = 41.36574669941303
                _width = 50

                _lbm.InitFlatInterface(n_g = _eq_params['n_g'], n_l = _eq_params['n_l'],
                                       width = _width,
                                       direction = 0, full_flag = False)

                print("Running the simulation...")

                _lbm.MainLoop(range(0, 2**20, 2**14),
                              convergence_functions = [CheckUConvergence])

                '''
                Getting a line from the density field
                '''
                _n = _lbm.sims_idpy_memory['n'].D2H()
                _n = _n.reshape(np.flip(_lbm.sims_vars['dim_sizes']))[0,:]
                _data_out.PushData(data = _n, key = _data_key)
                _data_out.Dump()

                print("Ending and deleting LB simulation object")
                _lbm.End()
                del _lbm
                print()
                print()

end = time.time()
def PrintElapsedTime(lapse):
    _n_sec_min, _n_min_hrs = 60, 60
    _n_sec_hrs, _n_hrs_day = _n_min_hrs * _n_sec_min, 24
    _n_sec_day = _n_hrs_day * _n_sec_hrs

    print(int(end - start)//_n_sec_day, "d, ",
          (int(end - start)//_n_sec_hrs)%_n_hrs_day, "h, ",
          (int(end - start)//_n_sec_min)%_n_min_hrs, "m, ",
          int(end - start)% _n_sec_min, "s")

PrintElapsedTime(start - end)
```

```python
# Figure 4 (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_4.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 4 (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_4.png")
```

## Figure 5

*Uncached Equilibrium Densities*

**0 d,  0 h,  22 m,  27 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

*Cached Equilibrium Densities*

**0 d,  0 h,  16 m,  21 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

```python
# Droplets Simulations for the Laplace data and velocity fields (click arrow to unfold)
import time
start = time.time()

import sys
sys.path.append("../../")

########################################################################

from pathlib import Path
reproduced_results = Path("reproduced-results")
if not reproduced_results.is_dir():
    reproduced_results.mkdir()

from sympy import exp as sp_exp
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from sympy import lambdify as sp_lambdify
from IPython.display import display
import numpy as np

from idpy.LBM.SCThermo import ShanChen
from idpy.Utils.ManageData import ManageData
from idpy.LBM.SCFStencils import SCFStencils, BasisVectors
from idpy.LBM.LBM import XIStencils

from idpy.LBM.SCThermo import ShanChanEquilibriumCache
from idpy.LBM.LBM import ShanChenMultiPhase, CheckUConvergence
from idpy.IdpyCode import IdpyMemory

def GetE2(stencil):
    weights_list = stencil.w_sol[0]
    _value = stencil.e_expr[2]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    return _value

'''
Here we declare the symbol for the density as 'n'
and define the pseudo-potential
'''

n = sp_symbols('n')
psis = [sp_exp(-1/n), 1 - sp_exp(-n)]
psi_codes = {psis[0]: 'exp((NType)(-1./ln))',
             psis[1]: '1. - exp(-(NType)ln)',}

Gs = {psis[0]: [-2.6, -3.1, -3.6],
      psis[1]: [-1.4, -1.6, -1.75]}

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")
E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

'''
Getting usual weights
'''

if True:
    '''
    E^6_F2P6
    '''
    S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4])
    S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

    '''
    E^8_F2P8
    '''
    S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])
    S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

    '''
    E^10_F2P10
    '''
    S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                               len_2s = [1, 2, 4, 5, 8, 9, 10])
    S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

    '''
    E^12_F2P12
    '''
    S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                               len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
    S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
Getting new weights: always up to w(8)
'''
if True:
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    eps_expr = (48*w4 + 96*w5 + 96*w8)
    eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

    '''
    E^6_F4P6
    '''
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

    '''
    E^8_P4F6
    '''
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    cond_e4 = S5_E8_P4F6.e_expr[4] - sp_Rational('4/7')
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('10/31')
    cond_chi_i = chi_i_expr

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E8_P4F6.typ_eq_s[4][0],
                      S5_E8_P4F6.typ_eq_s[6][0]]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

    '''
    E^10_P4F6
    '''
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    cond_e4 = S5_E10_P4F6.e_expr[4] - sp_Rational('12/17')
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('38/89')
    cond_chi_i = chi_i_expr

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E10_P4F6.typ_eq_s[4][0],
                       S5_E10_P4F6.typ_eq_s[6][0]]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

    '''
    E^12_P4F6
    '''
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    cond_e4 = S5_E12_P4F6.e_expr[4] - sp_Rational(120, 143)
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    cond_chi_i = chi_i_expr

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E12_P4F6.typ_eq_s[4][0],
                       S5_E12_P4F6.typ_eq_s[6][0]]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

'''
Defining output files: Flat interface
'''

stencil_string = {E6_P2F6_sym: 'E6_P2F6',
                  E6_P4F6_sym: 'E6_P4F6',
                  E8_P2F8_sym: 'E8_P2F8',
                  E8_P4F6_sym: 'E8_P4F6',
                  E10_P2F10_sym: 'E10_P2F10',
                  E10_P4F6_sym: 'E10_P4F6',
                  E12_P2F12_sym: 'E12_P2F12',
                  E12_P4F6_sym: 'E12_P4F6'}

stencil_dict = {E6_P2F6_sym: S5_E6_P2F6,
                E6_P4F6_sym: S5_E6_P4F6,
                E8_P2F8_sym: S5_E8_P2F8,
                E8_P4F6_sym: S5_E8_P4F6,
                E10_P2F10_sym: S5_E10_P2F10,
                E10_P4F6_sym: S5_E10_P4F6,
                E12_P2F12_sym: S5_E12_P2F12,
                E12_P4F6_sym: S5_E12_P4F6}

stencil_sym_list = [E6_P2F6_sym, E6_P4F6_sym,
                    E8_P2F8_sym, E8_P4F6_sym,
                    E10_P2F10_sym, E10_P4F6_sym,
                    E12_P2F12_sym, E12_P4F6_sym]

#stencil_sym_list = [E12_P2F12_sym, E12_P4F6_sym]

def LaplaceFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_laplace")

def DropletsFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_droplets")

def StencilPsiKey(stencil_sym, psi):
    return str(stencil_sym) + "_" + str(psi)

laplace_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[1])}

droplets_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[1])}

from idpy.Utils.ManageData import ManageData
'''
Simulation Loop
'''
for _psi in psis[0:1]:
    print("The pseudo-potential is: ")
    display(_psi)
    for _G in Gs[_psi][-1:]:
        print("Coupling Constant G: ", _G)
        for stencil_sym in stencil_sym_list:
            display(stencil_sym)
            _stencil = stencil_dict[stencil_sym]
            _data_out_laplace = ManageData(dump_file = laplace_files[StencilPsiKey(stencil_sym, _psi)])
            _data_out_droplets = ManageData(dump_file = droplets_files[StencilPsiKey(stencil_sym, _psi)])

            _e2_swap = GetE2(_stencil)
            print("e2: ", _e2_swap)

            for L in [255]:
                print("L: ", L, "R: ", L/5.)

                _data_key = str(_G) + "_" + str(L)

                _is_data_there_laplace = False
                if _data_out_laplace.Read():
                    print("File ", laplace_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                    print("Checking if data is there...")
                    _is_data_there_laplace = _data_key in _data_out_laplace.WhichData()

                    if _is_data_there_laplace:
                        print("Data Found! No need to run the simulation")
                        print("To perform the simulation again remove the file:")
                        print(laplace_files[StencilPsiKey(stencil_sym, _psi)])
                        print()

                _is_data_there_droplets = False
                if L in [255, 351]:
                    if _data_out_droplets.Read():
                        print("File ", droplets_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                        print("Checking if data is there...")
                        _is_data_there_droplets = _data_key in _data_out_droplets.WhichData()

                        if _is_data_there_droplets:
                            print("Data Found! No need to run the simulation")
                            print("To perform the simulation again remove the file:")
                            print(droplets_files[StencilPsiKey(stencil_sym, _psi)])
                            print()
                else:
                    _is_data_there_droplets = True

                if not _is_data_there_laplace or not _is_data_there_droplets:
                    print("Data is not there...")
                    print("Preparing the simulation...")
                    print()
                    _lbm = ShanChenMultiPhase(lang = lang,
                                              dim_sizes = (L, L),
                                              xi_stencil = XIStencils['D2Q9'],
                                              f_stencil = _stencil.PushStencil(),
                                              psi_code = psi_codes[_psi],
                                              psi_sym = _psi,
                                              SC_G = _G, tau = 1.,
                                              device = device,
                                              cl_kind = dev_kind,
                                              optimizer_flag = True)

                    print("----------------------------------------------------")
                    print("\nComputing/Retrieving flat mechanical equilibrium densities...")
                    print("psi: ", _psi, "G: ", _G, "Ws: ", _stencil.w_sol[0])

                    _sc_eq_cache = ShanChanEquilibriumCache(stencil = _stencil,
                                                            psi_f = _psi, G = _G,
                                                            c2 = XIStencils['D2Q9']['c2'])

                    _eq_params = _sc_eq_cache.GetFromCache()
                    print("...done!\n")
                    print("The mechanical equilibrium densities are:")
                    print("n_g (gas): ", _eq_params['n_g'],
                          ", n_l (liq): ", _eq_params['n_l'])
                    print("Surface tension: ", _eq_params['sigma_f'])
                    print("(G_c, n_c) (crtitcal G and n): ", (_eq_params['G_c'],
                                                              _eq_params['n_c']))
                    print()
                    print("Initializing flat interface")

                    _lbm.InitRadialInterface(n_g = _eq_params['n_g'], n_l = _eq_params['n_l'],
                                             R = L/5., full_flag = True)

                    print("Running the simulation...")

                    _lbm.MainLoop(range(0, 2**22, 2**14),
                                  convergence_functions = [CheckUConvergence])

                    '''
                    Getting average density, inner and outer density for
                    computing the Gibbs radius
                    '''

                    _n_ave = IdpyMemory.Sum(_lbm.sims_idpy_memory['n'])/(L*L)
                    _n = _lbm.sims_idpy_memory['n'].D2H()
                    _n = _n.reshape(np.flip(_lbm.sims_vars['dim_sizes']))

                    _center = tuple(_lbm.sims_vars['dim_center'])
                    _n_in = _n[_center]
                    _n_out = _n[(L - 1, L - 1)]

                    _R_Gibbs = L*np.sqrt((_n_ave - _n_out)/(np.pi * (_n_in - _n_out)))

                    def BulkP(n_value, _e2):
                        _psi_f = sp_lambdify(n, _psi)
                        p = n_value * XIStencils['D2Q9']['c2']
                        p += 0.5 * _G * _e2 * (_psi_f(n_value)) ** 2
                        return p

                    _p_in, _p_out = BulkP(_n_in, _e2_swap), BulkP(_n_out, _e2_swap)
                    _delta_p = _p_in - _p_out

                    print("L: ", L, "Start R: ", L/5., "R_Gibbs: ", _R_Gibbs)
                    print("Estimated surface tension: ", _delta_p * _R_Gibbs)
                    print("Analytic surface tension: ", _eq_params['sigma_f'])

                    _dict_out = {'delta_p': _delta_p,
                                 'R_Gibbs': _R_Gibbs}

                    if not _is_data_there_laplace:
                        _data_out_laplace.PushData(data = _dict_out, key = _data_key)
                        _data_out_laplace.Dump()

                    if not _is_data_there_droplets:
                        _u = _lbm.sims_idpy_memory['u'].D2H()
                        _data_out_droplets.PushData(data = _u, key = _data_key)
                        _data_out_droplets.Dump()

                    print("Ending and deleting LB simulation object")
                    _lbm.End()
                    del _lbm
                    print()
                    print()

end = time.time()
def PrintElapsedTime(lapse):
    _n_sec_min, _n_min_hrs = 60, 60
    _n_sec_hrs, _n_hrs_day = _n_min_hrs * _n_sec_min, 24
    _n_sec_day = _n_hrs_day * _n_sec_hrs

    print(int(end - start)//_n_sec_day, "d, ",
          (int(end - start)//_n_sec_hrs)%_n_hrs_day, "h, ",
          (int(end - start)//_n_sec_min)%_n_min_hrs, "m, ",
          int(end - start)% _n_sec_min, "s")

PrintElapsedTime(start - end)
```

### Figure 5 (a) \& (b)

```python
# Figure 5 (a), (b) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_5_ab.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 5 (a), (b) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_5_ab.png")
```

### Figure 5 (c) \& (d)

```python
# Figure 5 (c), (d) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_5_cd.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 5 (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_5_cd.png")
```

### Figure 5 (e) \& (f)

```python
# Figure 5 (e), (f) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_5_ef.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 5 (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_5_ef.png")
```

### Figure 5 (g) \& (h)

```python
# Figure 5 (g), (h) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_5_gh.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 5 (g), (h) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_5_gh.png")
```

## Figure 6

*Uncached Equilibrium Densities*

**0 d,  0 h,  38 m,  3 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

```python
# Droplets Simulations for the Laplace data and velocity fields (click arrow to unfold)
import time
start = time.time()

import sys
sys.path.append("../../")

########################################################################

from pathlib import Path
reproduced_results = Path("reproduced-results")
if not reproduced_results.is_dir():
    reproduced_results.mkdir()

from sympy import exp as sp_exp
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from sympy import lambdify as sp_lambdify
from IPython.display import display
import numpy as np

from idpy.LBM.SCThermo import ShanChen
from idpy.Utils.ManageData import ManageData
from idpy.LBM.SCFStencils import SCFStencils, BasisVectors
from idpy.LBM.LBM import XIStencils

from idpy.LBM.SCThermo import ShanChanEquilibriumCache
from idpy.LBM.LBM import ShanChenMultiPhase, CheckUConvergence
from idpy.IdpyCode import IdpyMemory

def GetE2(stencil):
    weights_list = stencil.w_sol[0]
    _value = stencil.e_expr[2]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    return _value

'''
Here we declare the symbol for the density as 'n'
and define the pseudo-potential
'''

n = sp_symbols('n')
psis = [sp_exp(-1/n), 1 - sp_exp(-n)]
psi_codes = {psis[0]: 'exp((NType)(-1./ln))',
             psis[1]: '1. - exp(-(NType)ln)',}

Gs = {psis[0]: [-2.6, -3.1, -3.6],
      psis[1]: [-1.4, -1.6, -1.75]}

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")
E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

'''
Getting usual weights
'''

if True:
    '''
    E^6_F2P6
    '''
    S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4])
    S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

    '''
    E^8_F2P8
    '''
    S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])
    S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

    '''
    E^10_F2P10
    '''
    S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                               len_2s = [1, 2, 4, 5, 8, 9, 10])
    S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

    '''
    E^12_F2P12
    '''
    S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                               len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
    S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
Getting new weights: always up to w(8)
'''
if True:
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    eps_expr = (48*w4 + 96*w5 + 96*w8)
    eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

    '''
    E^6_F4P6
    '''
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

    '''
    E^8_P4F6
    '''
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    cond_e4 = S5_E8_P4F6.e_expr[4] - sp_Rational('4/7')
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('10/31')
    cond_chi_i = chi_i_expr

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E8_P4F6.typ_eq_s[4][0],
                      S5_E8_P4F6.typ_eq_s[6][0]]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

    '''
    E^10_P4F6
    '''
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    cond_e4 = S5_E10_P4F6.e_expr[4] - sp_Rational('12/17')
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('38/89')
    cond_chi_i = chi_i_expr

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E10_P4F6.typ_eq_s[4][0],
                       S5_E10_P4F6.typ_eq_s[6][0]]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

    '''
    E^12_P4F6
    '''
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    cond_e4 = S5_E12_P4F6.e_expr[4] - sp_Rational(120, 143)
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    cond_chi_i = chi_i_expr

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E12_P4F6.typ_eq_s[4][0],
                       S5_E12_P4F6.typ_eq_s[6][0]]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

'''
Defining output files: Flat interface
'''

stencil_string = {E6_P2F6_sym: 'E6_P2F6',
                  E6_P4F6_sym: 'E6_P4F6',
                  E8_P2F8_sym: 'E8_P2F8',
                  E8_P4F6_sym: 'E8_P4F6',
                  E10_P2F10_sym: 'E10_P2F10',
                  E10_P4F6_sym: 'E10_P4F6',
                  E12_P2F12_sym: 'E12_P2F12',
                  E12_P4F6_sym: 'E12_P4F6'}

stencil_dict = {E6_P2F6_sym: S5_E6_P2F6,
                E6_P4F6_sym: S5_E6_P4F6,
                E8_P2F8_sym: S5_E8_P2F8,
                E8_P4F6_sym: S5_E8_P4F6,
                E10_P2F10_sym: S5_E10_P2F10,
                E10_P4F6_sym: S5_E10_P4F6,
                E12_P2F12_sym: S5_E12_P2F12,
                E12_P4F6_sym: S5_E12_P4F6}

stencil_sym_list = [E6_P2F6_sym, E6_P4F6_sym,
                    E8_P2F8_sym, E8_P4F6_sym,
                    E10_P2F10_sym, E10_P4F6_sym,
                    E12_P2F12_sym, E12_P4F6_sym]

#stencil_sym_list = [E12_P2F12_sym, E12_P4F6_sym]

def LaplaceFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_laplace")

def DropletsFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_droplets")

def StencilPsiKey(stencil_sym, psi):
    return str(stencil_sym) + "_" + str(psi)

laplace_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[1])}

droplets_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[1])}

from idpy.Utils.ManageData import ManageData
'''
Simulation Loop
'''
for _psi in psis:
    print("The pseudo-potential is: ")
    display(_psi)
    for _G in Gs[_psi][-1:]:
        print("Coupling Constant G: ", _G)
        for stencil_sym in stencil_sym_list:
            display(stencil_sym)
            _stencil = stencil_dict[stencil_sym]
            _data_out_laplace = ManageData(dump_file = laplace_files[StencilPsiKey(stencil_sym, _psi)])
            _data_out_droplets = ManageData(dump_file = droplets_files[StencilPsiKey(stencil_sym, _psi)])

            _e2_swap = GetE2(_stencil)
            print("e2: ", _e2_swap)

            for L in [255]:
                print("L: ", L, "R: ", L/5.)

                _data_key = str(_G) + "_" + str(L)

                _is_data_there_laplace = False
                if _data_out_laplace.Read():
                    print("File ", laplace_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                    print("Checking if data is there...")
                    _is_data_there_laplace = _data_key in _data_out_laplace.WhichData()

                    if _is_data_there_laplace:
                        print("Data Found! No need to run the simulation")
                        print("To perform the simulation again remove the file:")
                        print(laplace_files[StencilPsiKey(stencil_sym, _psi)])
                        print()

                _is_data_there_droplets = False
                if L in [255, 351]:
                    if _data_out_droplets.Read():
                        print("File ", droplets_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                        print("Checking if data is there...")
                        _is_data_there_droplets = _data_key in _data_out_droplets.WhichData()

                        if _is_data_there_droplets:
                            print("Data Found! No need to run the simulation")
                            print("To perform the simulation again remove the file:")
                            print(droplets_files[StencilPsiKey(stencil_sym, _psi)])
                            print()
                else:
                    _is_data_there_droplets = True

                if not _is_data_there_laplace or not _is_data_there_droplets:
                    print("Data is not there...")
                    print("Preparing the simulation...")
                    print()
                    _lbm = ShanChenMultiPhase(lang = lang,
                                              dim_sizes = (L, L),
                                              xi_stencil = XIStencils['D2Q9'],
                                              f_stencil = _stencil.PushStencil(),
                                              psi_code = psi_codes[_psi],
                                              psi_sym = _psi,
                                              SC_G = _G, tau = 1.,
                                              device = device,
                                              cl_kind = dev_kind,
                                              optimizer_flag = True)

                    print("----------------------------------------------------")
                    print("\nComputing/Retrieving flat mechanical equilibrium densities...")
                    print("psi: ", _psi, "G: ", _G, "Ws: ", _stencil.w_sol[0])

                    _sc_eq_cache = ShanChanEquilibriumCache(stencil = _stencil,
                                                            psi_f = _psi, G = _G,
                                                            c2 = XIStencils['D2Q9']['c2'])

                    _eq_params = _sc_eq_cache.GetFromCache()
                    print("...done!\n")
                    print("The mechanical equilibrium densities are:")
                    print("n_g (gas): ", _eq_params['n_g'],
                          ", n_l (liq): ", _eq_params['n_l'])
                    print("Surface tension: ", _eq_params['sigma_f'])
                    print("(G_c, n_c) (crtitcal G and n): ", (_eq_params['G_c'],
                                                              _eq_params['n_c']))
                    print()
                    print("Initializing flat interface")

                    _lbm.InitRadialInterface(n_g = _eq_params['n_g'], n_l = _eq_params['n_l'],
                                             R = L/5., full_flag = True)

                    print("Running the simulation...")

                    _lbm.MainLoop(range(0, 2**22, 2**14),
                                  convergence_functions = [CheckUConvergence])

                    '''
                    Getting average density, inner and outer density for
                    computing the Gibbs radius
                    '''

                    _n_ave = IdpyMemory.Sum(_lbm.sims_idpy_memory['n'])/(L*L)
                    _n = _lbm.sims_idpy_memory['n'].D2H()
                    _n = _n.reshape(np.flip(_lbm.sims_vars['dim_sizes']))

                    _center = tuple(_lbm.sims_vars['dim_center'])
                    _n_in = _n[_center]
                    _n_out = _n[(L - 1, L - 1)]

                    _R_Gibbs = L*np.sqrt((_n_ave - _n_out)/(np.pi * (_n_in - _n_out)))

                    def BulkP(n_value, _e2):
                        _psi_f = sp_lambdify(n, _psi)
                        p = n_value * XIStencils['D2Q9']['c2']
                        p += 0.5 * _G * _e2 * (_psi_f(n_value)) ** 2
                        return p

                    _p_in, _p_out = BulkP(_n_in, _e2_swap), BulkP(_n_out, _e2_swap)
                    _delta_p = _p_in - _p_out

                    print("L: ", L, "Start R: ", L/5., "R_Gibbs: ", _R_Gibbs)
                    print("Estimated surface tension: ", _delta_p * _R_Gibbs)
                    print("Analytic surface tension: ", _eq_params['sigma_f'])

                    _dict_out = {'delta_p': _delta_p,
                                 'R_Gibbs': _R_Gibbs}

                    if not _is_data_there_laplace:
                        _data_out_laplace.PushData(data = _dict_out, key = _data_key)
                        _data_out_laplace.Dump()

                    if not _is_data_there_droplets:
                        _u = _lbm.sims_idpy_memory['u'].D2H()
                        _data_out_droplets.PushData(data = _u, key = _data_key)
                        _data_out_droplets.Dump()

                    print("Ending and deleting LB simulation object")
                    _lbm.End()
                    del _lbm
                    print()
                    print()

end = time.time()
def PrintElapsedTime(lapse):
    _n_sec_min, _n_min_hrs = 60, 60
    _n_sec_hrs, _n_hrs_day = _n_min_hrs * _n_sec_min, 24
    _n_sec_day = _n_hrs_day * _n_sec_hrs

    print(int(end - start)//_n_sec_day, "d, ",
          (int(end - start)//_n_sec_hrs)%_n_hrs_day, "h, ",
          (int(end - start)//_n_sec_min)%_n_min_hrs, "m, ",
          int(end - start)% _n_sec_min, "s")

PrintElapsedTime(start - end)
```

### Figure 6 (a), (b), (c) and (d)

```python
# Figure 6 (a), (b), (c) and (d) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run "figure_6_abcd.py" {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 6 (a), (b), (c) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_6_abcd.png")
```

### Figure 6 (e), (f), (g) and (h)

```python
# Figure 6 (e), (f), (g) and (h) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run "figure_6_efgh.py" {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 6 (e), (f), (g), (h) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_6_efgh.png")
```

## Figure 7

*Cached Equilibrium Densities*

**0 d,  1 h,  9 m,  59 s, OpenCL, AMD Radeon RX 590 (64-bits), Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz**

```python
# Droplets Simulations for the Laplace data and velocity fields (click arrow to unfold)
import time
start = time.time()

import sys
sys.path.append("../../")

########################################################################

from pathlib import Path
reproduced_results = Path("reproduced-results")
if not reproduced_results.is_dir():
    reproduced_results.mkdir()

from sympy import exp as sp_exp
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from sympy import lambdify as sp_lambdify
from IPython.display import display
import numpy as np

from idpy.LBM.SCThermo import ShanChen
from idpy.Utils.ManageData import ManageData
from idpy.LBM.SCFStencils import SCFStencils, BasisVectors
from idpy.LBM.LBM import XIStencils

from idpy.LBM.SCThermo import ShanChanEquilibriumCache
from idpy.LBM.LBM import ShanChenMultiPhase, CheckUConvergence
from idpy.IdpyCode import IdpyMemory

def GetE2(stencil):
    weights_list = stencil.w_sol[0]
    _value = stencil.e_expr[2]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    return _value

'''
Here we declare the symbol for the density as 'n'
and define the pseudo-potential
'''

n = sp_symbols('n')
psis = [sp_exp(-1/n), 1 - sp_exp(-n)]
psi_codes = {psis[0]: 'exp((NType)(-1./ln))',
             psis[1]: '1. - exp(-(NType)ln)',}

Gs = {psis[0]: [-2.6, -3.1, -3.6],
      psis[1]: [-1.4, -1.6, -1.75]}

E6_P2F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P2\,F6}")
E8_P2F8_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P2\,F8}")
E10_P2F10_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P2\,F10}")
E12_P2F12_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P2\,F12}")
E6_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(6)}_{P4\,F6}")
E8_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(8)}_{P4\,F6}")
E10_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(10)}_{P4\,F6}")
E12_P4F6_sym = sp_symbols("\\boldsymbol{E}^{(12)}_{P4\,F6}")

'''
Getting usual weights
'''

if True:
    '''
    E^6_F2P6
    '''
    S5_E6_P2F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4])
    S5_E6_P2F6_W = S5_E6_P2F6.FindWeights()

    '''
    E^8_F2P8
    '''
    S5_E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])
    S5_E8_P2F8_W = S5_E8_P2F8.FindWeights()

    '''
    E^10_F2P10
    '''
    S5_E10_P2F10 = SCFStencils(E = BasisVectors(x_max = 3),
                               len_2s = [1, 2, 4, 5, 8, 9, 10])
    S5_E10_P2F10_W = S5_E10_P2F10.FindWeights()

    '''
    E^12_F2P12
    '''
    S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                               len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])
    S5_E12_P2F12_W = S5_E12_P2F12.FindWeights()

'''
Getting new weights: always up to w(8)
'''
if True:
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    eps_expr = (48*w4 + 96*w5 + 96*w8)
    eps_expr /= (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8)

    '''
    E^6_F4P6
    '''
    S5_E6_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E6_P4F6.GetWolfEqs()
    S5_E6_P4F6.GetTypEqs()

    cond_e4 = S5_E6_P4F6.e_expr[4] - sp_Rational('2/5')
    cond_e2 = S5_E6_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('2/17')
    cond_chi_i = chi_i_expr

    S5_E6_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E6_P4F6.typ_eq_s[4][0],
                      S5_E6_P4F6.typ_eq_s[6][0]]

    S5_E6_P4F6_W = S5_E6_P4F6.FindWeights(S5_E6_P4F6_eqs)

    '''
    E^8_P4F6
    '''
    S5_E8_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                             len_2s = [1, 2, 4, 5, 8])

    S5_E8_P4F6.GetWolfEqs()
    S5_E8_P4F6.GetTypEqs()

    cond_e4 = S5_E8_P4F6.e_expr[4] - sp_Rational('4/7')
    cond_e2 = S5_E8_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('10/31')
    cond_chi_i = chi_i_expr

    S5_E8_P4F6_eqs = [cond_e2,
                      cond_eps,
                      cond_chi_i,
                      S5_E8_P4F6.typ_eq_s[4][0],
                      S5_E8_P4F6.typ_eq_s[6][0]]

    S5_E8_P4F6_W = S5_E8_P4F6.FindWeights(S5_E8_P4F6_eqs)

    '''
    E^10_P4F6
    '''
    S5_E10_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E10_P4F6.GetWolfEqs()
    S5_E10_P4F6.GetTypEqs()

    cond_e4 = S5_E10_P4F6.e_expr[4] - sp_Rational('12/17')
    cond_e2 = S5_E10_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational('38/89')
    cond_chi_i = chi_i_expr

    S5_E10_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E10_P4F6.typ_eq_s[4][0],
                       S5_E10_P4F6.typ_eq_s[6][0]]

    S5_E10_P4F6_W = S5_E10_P4F6.FindWeights(S5_E10_P4F6_eqs)

    '''
    E^12_P4F6
    '''
    S5_E12_P4F6 = SCFStencils(E = BasisVectors(x_max = 2),
                              len_2s = [1, 2, 4, 5, 8])

    S5_E12_P4F6.GetWolfEqs()
    S5_E12_P4F6.GetTypEqs()

    cond_e4 = S5_E12_P4F6.e_expr[4] - sp_Rational(120, 143)
    cond_e2 = S5_E12_P4F6.e_expr[2] - 1
    cond_eps = eps_expr - sp_Rational(136774, 271813)
    cond_chi_i = chi_i_expr

    S5_E12_P4F6_eqs = [cond_e2,
                       cond_eps,
                       cond_chi_i,
                       S5_E12_P4F6.typ_eq_s[4][0],
                       S5_E12_P4F6.typ_eq_s[6][0]]

    S5_E12_P4F6_W = S5_E12_P4F6.FindWeights(S5_E12_P4F6_eqs)

'''
Defining output files: Flat interface
'''

stencil_string = {E6_P2F6_sym: 'E6_P2F6',
                  E6_P4F6_sym: 'E6_P4F6',
                  E8_P2F8_sym: 'E8_P2F8',
                  E8_P4F6_sym: 'E8_P4F6',
                  E10_P2F10_sym: 'E10_P2F10',
                  E10_P4F6_sym: 'E10_P4F6',
                  E12_P2F12_sym: 'E12_P2F12',
                  E12_P4F6_sym: 'E12_P4F6'}

stencil_dict = {E6_P2F6_sym: S5_E6_P2F6,
                E6_P4F6_sym: S5_E6_P4F6,
                E8_P2F8_sym: S5_E8_P2F8,
                E8_P4F6_sym: S5_E8_P4F6,
                E10_P2F10_sym: S5_E10_P2F10,
                E10_P4F6_sym: S5_E10_P4F6,
                E12_P2F12_sym: S5_E12_P2F12,
                E12_P4F6_sym: S5_E12_P4F6}

stencil_sym_list = [E6_P2F6_sym, E6_P4F6_sym,
                    E8_P2F8_sym, E8_P4F6_sym,
                    E10_P2F10_sym, E10_P4F6_sym,
                    E12_P2F12_sym, E12_P4F6_sym]

#stencil_sym_list = [E12_P2F12_sym, E12_P4F6_sym]

def LaplaceFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_laplace")

def DropletsFileName(stencil_sym, psi):
    psi_str = str(psi).replace("/", "_").replace("-", "_")
    psi_str = psi_str.replace(" ", "_")
    psi_str = psi_str.replace("(", "").replace(")","")
    lang_str = str(lang) + "_" + device_str

    return (lang_str + stencil_string[stencil_sym] + "_" +
            psi_str + "_droplets")

def StencilPsiKey(stencil_sym, psi):
    return str(stencil_sym) + "_" + str(psi)

laplace_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / LaplaceFileName(E12_P4F6_sym, psis[1])}

droplets_files = \
    {StencilPsiKey(E6_P2F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[0]),
     StencilPsiKey(E6_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[0]),
     StencilPsiKey(E8_P2F8_sym, psis[0]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[0]),
     StencilPsiKey(E8_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[0]),
     StencilPsiKey(E10_P2F10_sym, psis[0]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[0]),
     StencilPsiKey(E10_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[0]),
     StencilPsiKey(E12_P2F12_sym, psis[0]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[0]),
     StencilPsiKey(E12_P4F6_sym, psis[0]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[0]),
     StencilPsiKey(E6_P2F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P2F6_sym, psis[1]),
     StencilPsiKey(E6_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E6_P4F6_sym, psis[1]),
     StencilPsiKey(E8_P2F8_sym, psis[1]): reproduced_results / DropletsFileName(E8_P2F8_sym, psis[1]),
     StencilPsiKey(E8_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E8_P4F6_sym, psis[1]),
     StencilPsiKey(E10_P2F10_sym, psis[1]): reproduced_results / DropletsFileName(E10_P2F10_sym, psis[1]),
     StencilPsiKey(E10_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E10_P4F6_sym, psis[1]),
     StencilPsiKey(E12_P2F12_sym, psis[1]): reproduced_results / DropletsFileName(E12_P2F12_sym, psis[1]),
     StencilPsiKey(E12_P4F6_sym, psis[1]): reproduced_results / DropletsFileName(E12_P4F6_sym, psis[1])}

from idpy.Utils.ManageData import ManageData
'''
Simulation Loop
'''
for _psi in psis:
    print("The pseudo-potential is: ")
    display(_psi)
    for _G in Gs[_psi][-1:]:
        print("Coupling Constant G: ", _G)
        for stencil_sym in stencil_sym_list:
            display(stencil_sym)
            _stencil = stencil_dict[stencil_sym]
            _data_out_laplace = ManageData(dump_file = laplace_files[StencilPsiKey(stencil_sym, _psi)])
            _data_out_droplets = ManageData(dump_file = droplets_files[StencilPsiKey(stencil_sym, _psi)])

            _e2_swap = GetE2(_stencil)
            print("e2: ", _e2_swap)

            for L in [255, 351]:
                print("L: ", L, "R: ", L/5.)

                _data_key = str(_G) + "_" + str(L)

                _is_data_there_laplace = False
                if _data_out_laplace.Read():
                    print("File ", laplace_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                    print("Checking if data is there...")
                    _is_data_there_laplace = _data_key in _data_out_laplace.WhichData()

                    if _is_data_there_laplace:
                        print("Data Found! No need to run the simulation")
                        print("To perform the simulation again remove the file:")
                        print(laplace_files[StencilPsiKey(stencil_sym, _psi)])
                        print()

                _is_data_there_droplets = False
                if L in [255, 351]:
                    if _data_out_droplets.Read():
                        print("File ", droplets_files[StencilPsiKey(stencil_sym, _psi)], "exists!")
                        print("Checking if data is there...")
                        _is_data_there_droplets = _data_key in _data_out_droplets.WhichData()

                        if _is_data_there_droplets:
                            print("Data Found! No need to run the simulation")
                            print("To perform the simulation again remove the file:")
                            print(droplets_files[StencilPsiKey(stencil_sym, _psi)])
                            print()
                else:
                    _is_data_there_droplets = True

                if not _is_data_there_laplace or not _is_data_there_droplets:
                    print("Data is not there...")
                    print("Preparing the simulation...")
                    print()
                    _lbm = ShanChenMultiPhase(lang = lang,
                                              dim_sizes = (L, L),
                                              xi_stencil = XIStencils['D2Q9'],
                                              f_stencil = _stencil.PushStencil(),
                                              psi_code = psi_codes[_psi],
                                              psi_sym = _psi,
                                              SC_G = _G, tau = 1.,
                                              device = device,
                                              cl_kind = dev_kind,
                                              optimizer_flag = True)

                    print("----------------------------------------------------")
                    print("\nComputing/Retrieving flat mechanical equilibrium densities...")
                    print("psi: ", _psi, "G: ", _G, "Ws: ", _stencil.w_sol[0])

                    _sc_eq_cache = ShanChanEquilibriumCache(stencil = _stencil,
                                                            psi_f = _psi, G = _G,
                                                            c2 = XIStencils['D2Q9']['c2'])

                    _eq_params = _sc_eq_cache.GetFromCache()
                    print("...done!\n")
                    print("The mechanical equilibrium densities are:")
                    print("n_g (gas): ", _eq_params['n_g'],
                          ", n_l (liq): ", _eq_params['n_l'])
                    print("Surface tension: ", _eq_params['sigma_f'])
                    print("(G_c, n_c) (crtitcal G and n): ", (_eq_params['G_c'],
                                                              _eq_params['n_c']))
                    print()
                    print("Initializing flat interface")

                    _lbm.InitRadialInterface(n_g = _eq_params['n_g'], n_l = _eq_params['n_l'],
                                             R = L/5., full_flag = True)

                    print("Running the simulation...")

                    _lbm.MainLoop(range(0, 2**22, 2**14),
                                  convergence_functions = [CheckUConvergence])

                    '''
                    Getting average density, inner and outer density for
                    computing the Gibbs radius
                    '''

                    _n_ave = IdpyMemory.Sum(_lbm.sims_idpy_memory['n'])/(L*L)
                    _n = _lbm.sims_idpy_memory['n'].D2H()
                    _n = _n.reshape(np.flip(_lbm.sims_vars['dim_sizes']))

                    _center = tuple(_lbm.sims_vars['dim_center'])
                    _n_in = _n[_center]
                    _n_out = _n[(L - 1, L - 1)]

                    _R_Gibbs = L*np.sqrt((_n_ave - _n_out)/(np.pi * (_n_in - _n_out)))

                    def BulkP(n_value, _e2):
                        _psi_f = sp_lambdify(n, _psi)
                        p = n_value * XIStencils['D2Q9']['c2']
                        p += 0.5 * _G * _e2 * (_psi_f(n_value)) ** 2
                        return p

                    _p_in, _p_out = BulkP(_n_in, _e2_swap), BulkP(_n_out, _e2_swap)
                    _delta_p = _p_in - _p_out

                    print("L: ", L, "Start R: ", L/5., "R_Gibbs: ", _R_Gibbs)
                    print("Estimated surface tension: ", _delta_p * _R_Gibbs)
                    print("Analytic surface tension: ", _eq_params['sigma_f'])

                    _dict_out = {'delta_p': _delta_p,
                                 'R_Gibbs': _R_Gibbs}

                    if not _is_data_there_laplace:
                        _data_out_laplace.PushData(data = _dict_out, key = _data_key)
                        _data_out_laplace.Dump()

                    if not _is_data_there_droplets:
                        _u = _lbm.sims_idpy_memory['u'].D2H()
                        _data_out_droplets.PushData(data = _u, key = _data_key)
                        _data_out_droplets.Dump()

                    print("Ending and deleting LB simulation object")
                    _lbm.End()
                    del _lbm
                    print()
                    print()

end = time.time()
def PrintElapsedTime(lapse):
    _n_sec_min, _n_min_hrs = 60, 60
    _n_sec_hrs, _n_hrs_day = _n_min_hrs * _n_sec_min, 24
    _n_sec_day = _n_hrs_day * _n_sec_hrs

    print(int(end - start)//_n_sec_day, "d, ",
          (int(end - start)//_n_sec_hrs)%_n_hrs_day, "h, ",
          (int(end - start)//_n_sec_min)%_n_min_hrs, "m, ",
          int(end - start)% _n_sec_min, "s")

PrintElapsedTime(start - end)
```

### Figure 7 (a) and (b)

```python
# Figure 7 (a) and (b) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_7_ab.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 7 (a), (b) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_7_ab.png")
```

### Figure 7 (c) and (d)

```python
# Figure 7 (c) and (d) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_7_cd.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 7 (c), (d) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_7_cd.png")
```

### Figure 7 (e) and (f)

```python
# Figure 7 (e) and (f) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_7_ef.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 7 (e), (f) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
## Make sure
from IPython import display
display.Image("./reproduced-figures/figure_7_ef.png")
```

### Figure 7 (g) and (h)

```python
# Figure 7 (g) and (h) (click arrow to unfold)
'''
last entry (100) is for the resolution in DPI
the value is 200 for the published version
'''
_args = [device_str, lang, 100]
print(_args)
%run figure_7_gh.py {_args[0]} {_args[1]} {_args[2]}
```

```python
# Display Figure 7 (g), (h) (click arrow to unfold + see comment below)
## After displaying the image make sure to go to "Kernel -> Restart & Clear Output"
## It looks like a bug prevents codefolding/table of contents to load properly if
## the notebook is saved and restored with the images opened.
from IPython import display
display.Image("./reproduced-figures/figure_7_gh.png")
```

## Appendix

### Appendix C

Here we report the symbolic calculations presented in Appendix C, concerning the expressions of the weights $W(\ell)$ as a function of the isotropy constants $e_{2n}$ and $\varepsilon$.

```python
# Equations (C1), (C2), (C3) and (C4) (click arrow to unfold)
import sys
sys.path.append("../../")
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from sympy.solvers import solve
from sympy import simplify, collect, cancel, apart, factor, ratsimp, together

from IPython.display import display

from idpy.LBM.SCFStencils import SCFStencils, BasisVectors

def GetEpsilonExpr(stencil):
    '''
    GetVarepsilon:
    function for printing the value of \varepsilon
    the 5 weights expression is assumed
    '''
    w1, w2, w4, w5, w8, w9, w10 = sp_symbols("w(1) w(2) w(4) w(5) w(8) w(9) w(10)")
    w13, w16, w17 = sp_symbols("w(13) w(16) w(17)")
    eps = sp_symbols('\\varepsilon')
    w_sym_list = [w1, w2, w4, w5, w8, w9, w10, w13, w16, w17]

    eps_expr = \
        (48*w4 + 96*w5 + 96*w8 + 288*w9 + 576*w10 + 704*w13 + 960*w16 + 1920*w17)/\
        (6*w1 + 12*w2 + 72*w4 + 156*w5 + 144*w8 + 342*w9 +
         696*w10 + 812*w13 + 1056*w16 + 2124*w17)

    for i in range(10 - len(stencil.w_sym_list)):
        eps_expr = eps_expr.subs(w_sym_list[len(stencil.w_sym_list) + i], 0)

    return eps_expr

def GetIsotropyConditionsSym(stencil, order, weights_list = None):
    '''
    GetIsotropyConditions:
    function for printing the isotropy conditions
    for a given stencil and an arbitrary set of weights
    compatibly with the stencil. If no set of weights
    is provided the solution already provided by the
    stencil class is used
    '''
    if weights_list is None:
        weights_list = stencil.w_sol[0]

    _swap_sym = sp_symbols('I_{' + str(order) + '\,0}')
    display(_swap_sym)
    display(stencil.B2n_expr[order][0])
    _value = stencil.B2n_expr[order][0]
    for w_i in range(len(stencil.w_sym_list)):
        _value = _value.subs(stencil.w_sym_list[w_i],
                             weights_list[w_i])
    display(together(ratsimp(simplify(_value))))
    print()

    for i in range(len(stencil.B2q_expr[order])):
        _swap_sym = sp_symbols('I_{' + str(order) + '\,' + str(i + 1) + '}')
        display(_swap_sym)
        display(stencil.B2q_expr[order][i])
        _value = stencil.B2q_expr[order][i]
        for w_i in range(len(stencil.w_sym_list)):
            _value = _value.subs(stencil.w_sym_list[w_i],
                                 weights_list[w_i])
        display(together(ratsimp(simplify(_value))))
        print()

def GetLambdaChiSym(weights_list):
    w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
    chi_i, lambda_i = sp_symbols('\\chi_I \\Lambda_I')
    w_sym_list = [w1, w2, w4, w5, w8]

    chi_i_expr = 2*w4 - 8*w8 - w5
    lambda_i_expr = sp_Rational('1/2')*w1 - 2*w2 + 6*w4 - 24*w8 - 6*w5

    display(lambda_i)
    display(lambda_i_expr)
    _value = lambda_i_expr
    for w_i in range(len(weights_list)):
        _value = _value.subs(w_sym_list[w_i],
                             weights_list[w_i])
    display(together(ratsimp(simplify(_value))))
    print()

    display(chi_i)
    display(chi_i_expr)
    _value = chi_i_expr
    for w_i in range(len(weights_list)):
        _value = _value.subs(w_sym_list[w_i],
                             weights_list[w_i])
    display(together(ratsimp(simplify(_value))))
    print()

E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                      len_2s = [1, 2, 4, 5, 8])

E8_P2F8.GetWolfEqs()
E8_P2F8.GetTypEqs()

eps_expr = GetEpsilonExpr(E8_P2F8)
eps_sym = sp_symbols("\\varepsilon")

equations = []
equations += [E8_P2F8.e_sym[2] - E8_P2F8.e_expr[2]]
equations += [E8_P2F8.e_sym[4] - E8_P2F8.e_expr[4]]
equations += [E8_P2F8.e_sym[6] - E8_P2F8.e_expr[6]]
equations += [E8_P2F8.e_sym[8] - E8_P2F8.e_expr[8]]
equations += [eps_sym - eps_expr]

print("The system of equations reads (=0 is understood):")
for _eq in equations:
    display(_eq)

print()
print()
print("These are the solutions:")
w_sol = solve(equations, E8_P2F8.w_sym_list)
w_sol_nice = {}

for _w_sym in E8_P2F8.w_sym_list:
    w_sol_nice[_w_sym] = together(ratsimp(w_sol[_w_sym]))
    display(_w_sym)
    display(w_sol_nice[_w_sym])

print()
print()
print("The forcing isotropy conditions read:")
w_sol_nice_list = [w_sol_nice[_w_sym] for _w_sym in w_sol_nice]

GetIsotropyConditionsSym(E8_P2F8, 4, w_sol_nice_list)
GetIsotropyConditionsSym(E8_P2F8, 6, w_sol_nice_list)
GetIsotropyConditionsSym(E8_P2F8, 8, w_sol_nice_list)

print()
print()
print("The 4-th order pressure tensor isotropy conditions read:")
GetLambdaChiSym(w_sol_nice_list)
```

### Appendix D
Here we provide the connection between Equation (D14) and (D15)

```python
# Verification of equation (D15) (click arrow to unfold)
import sys
sys.path.append("../../")
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from IPython.display import display

from idpy.LBM.SCFStencils import SCFStencils, BasisVectors

S5_E12_P2F12 = SCFStencils(E = BasisVectors(x_max = 4),
                           len_2s = [1, 2, 4, 5, 8, 9, 10, 13, 16, 17])

S5_E12_P2F12.GetWolfEqs()
S5_E12_P2F12.FindWeights()

w1, w2, w4, w5, w8 = sp_symbols("w(1) w(2) w(4) w(5) w(8)")
w9, w10, w13, w16, w17 = sp_symbols("w(9) w(10) w(13) w(16) w(17)")

_sigma_c_E8 = -(w1 + 16*w4 + 18*w5)/2
_sigma_c_E10 = _sigma_c_E8 - (81*w9 + 128*w10)/2
_sigma_c_E12 = _sigma_c_E10 - (50*w13 + 256*w16 + 450*w17)/2

display(S5_E12_P2F12.e_sym[4])
display(S5_E12_P2F12.e_expr[4])

display(sp_symbols('I_{4\,0}'))
display(S5_E12_P2F12.typ_eq_s[4][0])

display(sp_symbols('\hat{\sigma}'))
display(_sigma_c_E12)
print("Now we verify Eq.(D15)")
display(-S5_E12_P2F12.e_sym[4]/2-sp_symbols('I_{4\,0}')/4)
display(-S5_E12_P2F12.e_expr[4]/2 - S5_E12_P2F12.typ_eq_s[4][0]/4)
```

### Appendix F

Here we show that starting from Eq. (F3) one can recover Eq. (F2). Since the coefficients of different vector groups are independent on each other, by checking a stencil yielding high order forcing isotropy one automatically checks the consistency of all lower orders.

The 14-th order for the forcing is imposed as a limit given the actual structure of the code which uses only the square length to label different vector groups. As discussed in the manuscript this is not sufficient as soon as $\ell > 25$

```python
# Check link between (F3) and (F2) (click arrow to unfold)
import sys
sys.path.append("../../")
from sympy import symbols as sp_symbols
from sympy import Rational as sp_Rational
from IPython.display import display

from idpy.LBM.SCFStencils import SCFStencils, BasisVectors

E14_P2F14 = SCFStencils(E = BasisVectors(x_max = 5),
                        len_2s = [1, 2,
                                  4,
                                  5, 8,
                                  9, 10,
                                  13, 16, 17,
                                  18, 20, 25])

E8_P2F8 = SCFStencils(E = BasisVectors(x_max = 2),
                      len_2s = [1, 2, 4, 5, 8])

print("Weights for the 14-th order isotropy stencil")
for elem in E14_P2F14.FindWeights():
    display(elem)
print()

print("Computing Eqs. (F2) and (F3)")
E14_P2F14.RecoverTypEqs()
E14_P2F14.GetTypEqs()
print()

print("Comparison")
for n2 in range(4, 16, 2):
    for k in range(len(E14_P2F14.rec_typ_eq_s[n2])):
        display(sp_symbols('\hat{I}_{' + str(n2) + '\,' + str(k) + '}'))
        print("Result from (F3): ")
        display(E14_P2F14.rec_typ_eq_s[n2][k])
        print("Corresponding in (F2): ")
        display(E14_P2F14.typ_eq_s[n2][k])
        print("Subtraction: ",
              E14_P2F14.rec_typ_eq_s[n2][k] - E14_P2F14.typ_eq_s[n2][k])
        print()
```

```python

```