Parameters#

The computational and physical parameters are handled by xcompact3d_toolbox.Parameters. It is built on top of Traitlets, which aims to make the parameters compatible with what XCompact3d expects, and also brings some advantages:

  • Attributes are type-checked;

  • Default values, restrictions and connections between related parameters are applied when necessary;

  • ‘On change’ callbacks for validation and observation;

  • Two-way linking with ipywidgets.

import numpy as np

import xcompact3d_toolbox as x3d

The first step is to establish numerical precision. Use np.float64 if Xcompact3d was compiled with the flag -DDOUBLE_PREC (check the Makefile), use np.float32 otherwise:

x3d.param["mytype"] = np.float32

Initialization#

There are a few ways to initialize the class. First, calling it with no arguments initializes all variables with default value:

prm = x3d.Parameters()

You can access a list with all the available variables at the Api reference.

Let’s see how it looks like:

print(prm)
! -*- mode: f90 -*-

!===================
&BasicParam
!===================

       C_filter = 0.49            ! 
           beta = 1.0             ! Refinement parameter
             dt = 0.001           ! Time step
          gravx = 0.0             ! Gravity unitary vector in x-direction
          gravy = 0.0             ! Gravity unitary vector in y-direction
          gravz = 0.0             ! Gravity unitary vector in z-direction
        ifilter = 0               ! 
         ifirst = 0               ! The number for the first iteration
           iibm = 0               ! Flag for immersed boundary method (0: No, 1: Yes)
            iin = 0               ! Defines perturbation at initial condition
          ilast = 0               ! The number for the last iteration
        ilesmod = 0               ! Enables Large-Eddy methodologies (0: No, 1: Yes)
           ilmn = .false.         ! 
   inflow_noise = 0.0             ! Turbulence intensity (1=100%) !! Inflow condition
     init_noise = 0.0             ! Turbulence intensity (1=100%) !! Initial condition
          ipost = 0               ! Enables online postprocessing at a frequency iprocessing (0: No, 1: Yes)
        iscalar = 0               ! 
         istret = 0               ! y mesh refinement (0:no, 1:center, 2:both sides, 3:bottom)
       iturbine = 0               ! 
          itype = 12              ! Flow configuration (1:Lock-exchange, 2:TGV, 3:Channel, and others)
          ivisu = 1               ! Enable store snapshots at a frequency ioutput (0: No, 1: Yes)
          nclx1 = 2               ! Velocity boundary condition where x=0
          nclxn = 2               ! Velocity boundary condition where x=xlx
          ncly1 = 2               ! Velocity boundary condition where y=0
          nclyn = 2               ! Velocity boundary condition where y=yly
          nclz1 = 2               ! Velocity boundary condition where z=0
          nclzn = 2               ! Velocity boundary condition where z=zlz
      numscalar = 0               ! Number of scalar fractions
             nx = 17              ! X-direction nodes
             ny = 17              ! Y-direction nodes
             nz = 17              ! Z-direction nodes
          p_col = 0               ! Column partition for domain decomposition and parallel computation
          p_row = 0               ! Row partition for domain decomposition and parallel computation
             re = 1000.0          ! Reynolds number
             u1 = 2.0             ! 
             u2 = 1.0             ! 
            xlx = 1.0             ! Size of the box in x-direction
            yly = 1.0             ! Size of the box in y-direction
            zlz = 1.0             ! Size of the box in z-direction

/End

!===================
&NumOptions
!===================

            cnu = 0.44            ! Ratio between hyperviscosity at km=2/3π and kc=π (dissipation factor range)
      ifirstder = 4               ! 
      iimplicit = 0               ! 
     isecondder = 4               ! Scheme for second order derivative
    itimescheme = 3               ! Time integration scheme (1: Euler, 2: AB2, 3: AB3, 5: RK3)
          nu0nu = 4.0             ! Ratio between hyperviscosity/viscosity at nu (dissipation factor intensity)

/End

!===================
&InOutParam
!===================

    icheckpoint = 1000            ! Frequency for writing backup file
       ioutflow = 0               ! 
        ioutput = 1000            ! Frequency for visualization file
    iprocessing = 1000            ! Frequency for online postprocessing
       irestart = 0               ! Read initial flow field (0: No, 1: Yes)
       ninflows = 1               ! 
        nprobes = 0               ! 
     ntimesteps = 1               ! 
          nvisu = 1               ! Size for visualization collection
       output2D = 0               ! 

/End

!===================
&Statistics
!===================


/End

!===================
&CASE
!===================


/End

It is possible to access and/or set values afterwards:

# Reynolds Number
print(prm.re)

# attribute new value
prm.re = 1e6
print(prm.re)
1000.0
1000000.0

Second, we can specify some values, and let the missing ones be initialized with default value:

prm = x3d.Parameters(
    filename="example.i3d",
    itype=10,
    nx=129,
    ny=65,
    nz=32,
    xlx=15.0,
    yly=10.0,
    zlz=3.0,
    nclx1=2,
    nclxn=2,
    ncly1=1,
    nclyn=1,
    nclz1=0,
    nclzn=0,
    iin=1,
    istret=2,
    re=300.0,
    init_noise=0.0125,
    inflow_noise=0.0125,
    dt=0.0025,
    ifirst=1,
    ilast=45000,
    irestart=0,
    icheckpoint=45000,
    ioutput=200,
    iprocessing=50,
)

It is easy to write example.i3d to disc, just type:

prm.write()

And finally, it is possible to read the parameters from the disc:

prm = x3d.Parameters(filename="example.i3d")
prm.load()

The same result is obtained in a more concise way:

prm = x3d.Parameters(loadfile="example.i3d")

The class can also read the previous parameters format (se more information here):

prm = x3d.Parameters(loadfile="incompact3d.prm")

There are extra objects to read and write the raw binary files from XCompact3d on-demand.

  • Read a binary field from the disc:

    ux = prm.dataset.load_array("ux-0000.bin")
    
  • Read the entire time series for a given variable:

    ux = prm.dataset.load_time_series("ux")
    
  • Read all variables for a given snapshot:

    snapshot = prm.dataset.load_snapshot(10)
    
  • Write xdmf files, so the binary files can be open in any external visualization tool:

    prm.dataset.write_xdmf()
    
  • Compute the coordinates, including support for mesh refinement in y:

prm.get_mesh()
{'x': array([ 0.       ,  0.1171875,  0.234375 ,  0.3515625,  0.46875  ,
         0.5859375,  0.703125 ,  0.8203125,  0.9375   ,  1.0546875,
         1.171875 ,  1.2890625,  1.40625  ,  1.5234375,  1.640625 ,
         1.7578125,  1.875    ,  1.9921875,  2.109375 ,  2.2265625,
         2.34375  ,  2.4609375,  2.578125 ,  2.6953125,  2.8125   ,
         2.9296875,  3.046875 ,  3.1640625,  3.28125  ,  3.3984375,
         3.515625 ,  3.6328125,  3.75     ,  3.8671875,  3.984375 ,
         4.1015625,  4.21875  ,  4.3359375,  4.453125 ,  4.5703125,
         4.6875   ,  4.8046875,  4.921875 ,  5.0390625,  5.15625  ,
         5.2734375,  5.390625 ,  5.5078125,  5.625    ,  5.7421875,
         5.859375 ,  5.9765625,  6.09375  ,  6.2109375,  6.328125 ,
         6.4453125,  6.5625   ,  6.6796875,  6.796875 ,  6.9140625,
         7.03125  ,  7.1484375,  7.265625 ,  7.3828125,  7.5      ,
         7.6171875,  7.734375 ,  7.8515625,  7.96875  ,  8.0859375,
         8.203125 ,  8.3203125,  8.4375   ,  8.5546875,  8.671875 ,
         8.7890625,  8.90625  ,  9.0234375,  9.140625 ,  9.2578125,
         9.375    ,  9.4921875,  9.609375 ,  9.7265625,  9.84375  ,
         9.9609375, 10.078125 , 10.1953125, 10.3125   , 10.4296875,
        10.546875 , 10.6640625, 10.78125  , 10.8984375, 11.015625 ,
        11.1328125, 11.25     , 11.3671875, 11.484375 , 11.6015625,
        11.71875  , 11.8359375, 11.953125 , 12.0703125, 12.1875   ,
        12.3046875, 12.421875 , 12.5390625, 12.65625  , 12.7734375,
        12.890625 , 13.0078125, 13.125    , 13.2421875, 13.359375 ,
        13.4765625, 13.59375  , 13.7109375, 13.828125 , 13.9453125,
        14.0625   , 14.1796875, 14.296875 , 14.4140625, 14.53125  ,
        14.6484375, 14.765625 , 14.8828125, 15.       ], dtype=float32),
 'y': array([ 0.        ,  0.04504655,  0.09029294,  0.13594207,  0.18220428,
         0.22930034,  0.27746603,  0.32695502,  0.3780484 ,  0.4310567 ,
         0.48632994,  0.5442658 ,  0.60532016,  0.6700244 ,  0.7389975 ,
         0.8129757 ,  0.89283013,  0.9796148 ,  1.0746001 ,  1.1793398 ,
         1.2957419 ,  1.426162  ,  1.5735211 ,  1.7414374 ,  1.9343703 ,
         2.157731  ,  2.417885  ,  2.7218597 ,  3.076467  ,  3.4864438 ,
         3.9514093 ,  4.462363  ,  5.        ,  5.537637  ,  6.0485907 ,
         6.5135565 ,  6.923533  ,  7.27814   ,  7.5821147 ,  7.842269  ,
         8.06563   ,  8.258562  ,  8.426478  ,  8.573838  ,  8.704258  ,
         8.820661  ,  8.9254    ,  9.020385  ,  9.10717   ,  9.187024  ,
         9.261003  ,  9.329975  ,  9.39468   ,  9.455734  ,  9.51367   ,
         9.568943  ,  9.621952  ,  9.673045  ,  9.722534  ,  9.7706995 ,
         9.817796  ,  9.864058  ,  9.909707  ,  9.954953  , 10.        ],
       dtype=float32),
 'z': array([0.     , 0.09375, 0.1875 , 0.28125, 0.375  , 0.46875, 0.5625 ,
        0.65625, 0.75   , 0.84375, 0.9375 , 1.03125, 1.125  , 1.21875,
        1.3125 , 1.40625, 1.5    , 1.59375, 1.6875 , 1.78125, 1.875  ,
        1.96875, 2.0625 , 2.15625, 2.25   , 2.34375, 2.4375 , 2.53125,
        2.625  , 2.71875, 2.8125 , 2.90625], dtype=float32)}

More details about I/O and array manipulations with xarray are available at:

Traitlets#

Type-checking#

All parameters are type-checked, to make sure that they are what XCompact3d expects. Use the cellcode below to see how a TraitError pops out when we try:

prm.itype = 10.5
prm.itype = -5
prm.itype = 20
prm.itype = 'sandbox'

Validation#

Some parameters, like mesh points (nx, ny and nz), trigger a validation operation when a new value is attributed to them. Due to restrictions at the FFT library, they must be equal to:

\[\begin{split} n_i = \left\{ \begin{array}{ll} 2^{1+a} \times 3^b \times 5^c &\mbox{if periodic,} \\ 2^{1+a} \times 3^b \times 5^c + 1 &\mbox{otherwise,} \end{array} \right. \end{split}\]

where \(a\), \(b\) and \(c\) are non negative integers. In addition, the derivatives stencil imposes that:

\[\begin{split} n_i \ge \left\{ \begin{array}{ll} 8 &\mbox{if periodic,} \\ 9 &\mbox{otherwise.} \end{array} \right. \end{split}\]

Again, give it a try at the cellcode below:

prm.nx = 129
prm.nx = 4
prm.nx = 60
prm.nx = 61

Observation#

Other parameters, like mesh resolution (dx, dy and dz), are automatically updated when any new attribution occurs to mesh points and/or domain size. Let’s create a quick print functions to play with:

def show_param():
    for var in "nclx1 nclxn nx xlx dx".split():
        print(f"{var:>5} = {getattr(prm, var)}")

We are starting with:

show_param()
nclx1 = 2
nclxn = 2
   nx = 129
  xlx = 15.0
   dx = 0.1171875

Let’s change just the domain’s length:

prm.xlx = 50.0

show_param()
nclx1 = 2
nclxn = 2
   nx = 129
  xlx = 50.0
   dx = 0.390625

The resolution was updated as well. Now the number of mesh points:

prm.nx = 121

show_param()
nclx1 = 2
nclxn = 2
   nx = 121
  xlx = 50.0
   dx = 0.4166666666666667

Again, the resolution was updated. Now we set a new mesh resolution, this time, xlx will be updated in order to satisfy the new resolution:

prm.dx = 1e-2

show_param()
nclx1 = 2
nclxn = 2
   nx = 121
  xlx = 1.2
   dx = 0.01

Boundary conditions are observed as well. Xcompact3d allows three different BC for velocity:

  • Periodic 0;

  • Free-slip 1;

  • Dirichlet 2.

They can be assigned individually for each of the six boundaries:

  • nclx1 and nclxn, where \(x=0\) and \(x=xlx\);

  • ncly1 and nclyn, where \(y=0\) and \(y=yly\);

  • nclz1 and nclzn, where \(z=0\) and \(z=zlz\).

It leads to 5 possibilities (00, 11, 12, 21 and 22), because both boundary must be periodic, or not, so 0 cannot be combined.

Let’s check it out, we are starting with:

show_param()
nclx1 = 2
nclxn = 2
   nx = 121
  xlx = 1.2
   dx = 0.01

We will change just one side to periodic (nclx1 = 0), for consistence, the other side should be periodic too. Let’s see:

prm.nclx1 = 0

show_param()
nclx1 = 0
nclxn = 0
   nx = 120
  xlx = 1.2
   dx = 0.01

Now free-slip in one side (nclx1 = 1), and the other should be non-periodic:

prm.nclx1 = 1

show_param()
nclx1 = 1
nclxn = 1
   nx = 121
  xlx = 1.2
   dx = 0.01

Setting the other boundary to periodic:

prm.nclxn = 0

show_param()
nclx1 = 0
nclxn = 0
   nx = 120
  xlx = 1.2
   dx = 0.01

and now back to Dirichlet:

prm.nclxn = 2

show_param()
nclx1 = 2
nclxn = 2
   nx = 121
  xlx = 1.2
   dx = 0.01

This time, free-slip:

prm.nclxn = 1

show_param()
nclx1 = 2
nclxn = 1
   nx = 121
  xlx = 1.2
   dx = 0.01

There was no need to update nclx1, because 1 and 2 can be freely combined. Notice that nx was modified properly from 121 to 120 and then back, according to the possible values, dx and xlx stayed untouched.

Metadata#

Traitlets types constructors have a tag method to store metadata in a dictionary. In the case of Xcompact3d-toolbox, two are especially useful:

  • group defines to what namespace a given parameter belongs when the class is written to .i3d file (.write() method) or read from .i3d or .prm files (.load() method), parameters without a group are ignored for both methods;

  • desc contains a brief description of each parameter that is shown on screen as we saw above, and also printed with the .write() method.

Declaring new parameters#

You probably would like to add more parameters for your own flow configuration, or because some of them were not implemented yet (it is a work in progress).

To do so, any Auxiliary variable can be included after initialization, like:

prm.my_variable = 0  # or any other datatype

It was called Auxiliary variable because, in this way, it will be available only for the Python application.

In order to include it at the .i3d file and make it available for XCompact3d, we can create a subclass that inherits all the functionality from xcompact3d_toolbox.Parameters:

import traitlets


# Create a class named MyParameters, which inherits the properties all properties and methods
class MyParameters(x3d.Parameters):
    # .tag with group and description guarantees that the new variable will
    # be compatible with all functionalities (like .write() and .load())
    my_variable = traitlets.Int(default_value=0, min=0).tag(
        group="BasicParam", desc="An example at the Tutorial <------"
    )

    # And a custom method, for instance
    def my_method(self):
        return self.my_variable * 2


prm = MyParameters(nx=257, ny=129, nz=31, my_variable=10)  # and here we go

# Testing the method
print(prm.my_method())

# Show all parameters on screen
print(prm)
20
! -*- mode: f90 -*-

!===================
&BasicParam
!===================

       C_filter = 0.49            ! 
           beta = 1.0             ! Refinement parameter
             dt = 0.001           ! Time step
          gravx = 0.0             ! Gravity unitary vector in x-direction
          gravy = 0.0             ! Gravity unitary vector in y-direction
          gravz = 0.0             ! Gravity unitary vector in z-direction
        ifilter = 0               ! 
         ifirst = 0               ! The number for the first iteration
           iibm = 0               ! Flag for immersed boundary method (0: No, 1: Yes)
            iin = 0               ! Defines perturbation at initial condition
          ilast = 0               ! The number for the last iteration
        ilesmod = 0               ! Enables Large-Eddy methodologies (0: No, 1: Yes)
           ilmn = .false.         ! 
   inflow_noise = 0.0             ! Turbulence intensity (1=100%) !! Inflow condition
     init_noise = 0.0             ! Turbulence intensity (1=100%) !! Initial condition
          ipost = 0               ! Enables online postprocessing at a frequency iprocessing (0: No, 1: Yes)
        iscalar = 0               ! 
         istret = 0               ! y mesh refinement (0:no, 1:center, 2:both sides, 3:bottom)
       iturbine = 0               ! 
          itype = 12              ! Flow configuration (1:Lock-exchange, 2:TGV, 3:Channel, and others)
          ivisu = 1               ! Enable store snapshots at a frequency ioutput (0: No, 1: Yes)
    my_variable = 10              ! An example at the Tutorial <------
          nclx1 = 2               ! Velocity boundary condition where x=0
          nclxn = 2               ! Velocity boundary condition where x=xlx
          ncly1 = 2               ! Velocity boundary condition where y=0
          nclyn = 2               ! Velocity boundary condition where y=yly
          nclz1 = 2               ! Velocity boundary condition where z=0
          nclzn = 2               ! Velocity boundary condition where z=zlz
      numscalar = 0               ! Number of scalar fractions
             nx = 257             ! X-direction nodes
             ny = 129             ! Y-direction nodes
             nz = 31              ! Z-direction nodes
          p_col = 0               ! Column partition for domain decomposition and parallel computation
          p_row = 0               ! Row partition for domain decomposition and parallel computation
             re = 1000.0          ! Reynolds number
             u1 = 2.0             ! 
             u2 = 1.0             ! 
            xlx = 1.0             ! Size of the box in x-direction
            yly = 1.0             ! Size of the box in y-direction
            zlz = 1.0             ! Size of the box in z-direction

/End

!===================
&NumOptions
!===================

            cnu = 0.44            ! Ratio between hyperviscosity at km=2/3π and kc=π (dissipation factor range)
      ifirstder = 4               ! 
      iimplicit = 0               ! 
     isecondder = 4               ! Scheme for second order derivative
    itimescheme = 3               ! Time integration scheme (1: Euler, 2: AB2, 3: AB3, 5: RK3)
          nu0nu = 4.0             ! Ratio between hyperviscosity/viscosity at nu (dissipation factor intensity)

/End

!===================
&InOutParam
!===================

    icheckpoint = 1000            ! Frequency for writing backup file
       ioutflow = 0               ! 
        ioutput = 1000            ! Frequency for visualization file
    iprocessing = 1000            ! Frequency for online postprocessing
       irestart = 0               ! Read initial flow field (0: No, 1: Yes)
       ninflows = 1               ! 
        nprobes = 0               ! 
     ntimesteps = 1               ! 
          nvisu = 1               ! Size for visualization collection
       output2D = 0               ! 

/End

!===================
&Statistics
!===================


/End

!===================
&CASE
!===================


/End

Take a look at the source code of parameters.py if you need more examples for different data types.

Graphical User Interface#

For an interactive experience launch this tutorial on Binder, the widgets are not so responsive when disconnected from a Python application.

To conclude this part of the tutorial, let’s see another option to handle the parameters. The class ParametersGui is a subclass of Parameters, and includes all the features described above. In addition, ParametersGui offers an user interface with IPywidgets.

It is still under development, more parameters and features are going to be included soon, as well as more widgets.

Just like before, we start with:

prm = x3d.ParametersGui()

Widgets are returned on demand when any instance of class ParametersGui is called, let’s see:

prm("nx", "xlx", "dx", "nclx1", "nclxn")

You can play around with the widgets above and see the effect of the observations made previously.

Notice that the Traitlets parameters are related to the value at their widgets in a two-way link, in this way, a print will show the actual value on the widgets:

show_param()
nclx1 = 2
nclxn = 2
   nx = 17
  xlx = 1.0
   dx = 0.0625

Give it a try, modify the values at the widgets and print them again.

It also works on the other way, set a new value to a parameters will change its widget, try it:

# prm.nclx1 = 0

And of course, different widgets for the same parameter are always synchronized, change the widget below and see what happens with the widget above:

prm("nx")

A last example is about the domain decomposition for parallel computation, Xcompact3d uses 2DECOMP&FFT. The available options for p_row and p_col are presented as functions of the number of computational cores ncores, notice that p_row * p_col = ncores should be respected and p_row * p_col = 0 activates the auto-tunning mode. The widgets are prepared to respect these restrictions:

prm("ncores", "p_row", "p_col")

To conclude this part of the tutorial, let’s see what happens when class ParametersGui is presented on screen, hover the mouse over some variable to see its description:

prm