Complex case

In this tutorial we will tackle a more complex scenario that includes several elements all at once. We will simulate the filling of a non-planar shape, arbitrarily oriented in space, that contains several different materials. This example uses features introduced in the Channel Flow and Anisotropy examples.

All files used in this tutorial are available in the tutorials/complex folder. We recommend you download the mesh alone and create the script yourself following the tutorial.

Note

If you have not yet completed the Channel Flow tutorial, we advise to do that first because here we will not cover in detail the steps that were already introduced.

The mesh

The mesh represents a strange-looking shape that extends in three dimensions:

../_images/lizzy_complex_case_layout.png

We can notice that none of the geometry edges are aligned with any global axis: the part is rotated arbitrarily in space, therefore defining orientations will be important for any anisotropic material we will use. The shape is composed by an L-shaped zone, which bends upwards into a ramp. In the first region we also have a 2 mm wide strip representing a racetrack on the side of the domain.

The mesh contains 4 domain tags:

  • Lshape: elements tag assigned to the L-shaped region

  • ramp: elements tag assigned to the ramp region

  • racetrack: elements tag assigned to the thin strip on the side of the L-shape

  • inlet: line tag assigned to the inlet edge of the part

We want to define the case as following:

  • The L-shaped region is isotropic.

  • The ramp region is anisotropic, with principal permeability \(k_1\) oriented along the diagonal direction indicated in the picture.

  • The racetrack is isotropic, has much higher permeability and half the thickness than the rest of the domain.

Preparing the working folder

Follow the same steps described in Channel Flow to set up the working folder and copy the mesh file into it. Create a new Python script — in this example we name it complex.py.

Setting up the model

Let’s import Lizzy and configure logging:

import lizzy

import logging
logging.basicConfig(level=logging.INFO)

Let’s create the LizzyModel and read the mesh:

model = lizzy.LizzyModel()
model.read_mesh_file("Complex.msh")
model.set_simulation_parameters(output_interval=10, progress_bar=True)

Let’s also define a resin with viscosity 0.1 Pa s:

model.create_resin("resin_01", 0.1)
model.assign_resin("resin_01")

So far, nothing new. However, things will get more interesting as now we will need to define anisotropic materials on geometries that are arbitrarily oriented in space.

Creating materials

We can start by creating the following materials:

model.create_material("material_iso", (1E-10, 1E-10, 1E-10), 0.5, 0.002)
model.create_material("material_aniso", (1E-10, 1E-11, 1E-11), 0.5, 0.002)
model.create_material("material_racetrack", (1E-7, 1E-7, 1E-7), 0.5, 0.001)

As we can see, one of the materials (material_aniso) is anisotropic by one order of magnitude between \(k_1\) and \(k_2\). Furthermore, the racetrack material has a much higher permeability (3 orders of magnitude higher than material_iso and half the thickness.

Defining orientations

We need to define an orientation for the anisotropic material material_aniso. In this special case, this is particularly important because the entire geometry is arbitrarily rotated in space, so the default rosette is useless. In the example Anisotropy we have seen how we can define a Rosette by passing an orientation vector. There are cases, however, when this is inconvenient and would require effort to calculate, as the components of the orientation vector may not be known before.

Luckily, the Rosette constructor can work with different input styles. In this case, the most convenient way is to use a 2-point initialisation. We can pass 2 arguments to Rosette, each being an (x, y, z) tuple of values that represents a point in space. The direction vector will be constructed as the line passing through them. Using the definition Rosette((x1,y1,z1), (x2,y2,z2)) we simply need to know the global coordinates of 2 points aligned in the direction of the \(k_1\) orientation. In our example, we can use a visualisation tool to measure our coordinates (example using Paraview):

../_images/node_1213_labeled.png

By inspecting the mesh we obtain the following information:

  • node 1: id = 12, coordinates = (1.017, 0.607, -0.196)

  • node 2: id = 13, coordinates = (1.780, 0.431, 0.018)

Conveniently, we won’t have to write down those numbers: if we know the index of a node, we can access the Node object from the model using the get_node_by_idx() method, and use the coords attribute of the node to get its (\(x, y, z\)) coordinates. Then calculating the direction vector becomes trivial:

dir = model.get_node_by_id(13).coords - model.get_node_by_id(12).coords

Now that we have our direction, we can create the rosette as usual:

rosette_ramp = model.create_rosette("rosette_ramp", dir)

Note

The Rosette APIs are being reworked and may change in a future update.

Assigning materials

Now that our orientation rosette for the anisotropic region is defined, we can proceed to assign all materials the usual way:

model.assign_material("material_iso", "Lshape")
model.assign_material("material_aniso", "ramp", "rosette_ramp")
model.assign_material("material_racetrack", "racetrack")

Completing the script

We can now conclude the script by assigning BCs and launching the solver. Nothing new here:

# BCs
model.create_pressure_inlet("inlet", 1E+05)
model.assign_inlet("inlet", "inlet")

# Solve
model.initialise_solver()
model.solve()

# Save results
model.save_results()

The full script

import lizzy as liz

model = liz.LizzyModel()
model.read_mesh_file("Complex_rotated.msh")

model.set_simulation_parameters(output_interval=100)
model.create_resin("resin", viscosity=0.1)
model.assign_resin("resin")

model.create_material("material_iso", (1E-10, 1E-10, 1E-10), 0.5, 1.0)
model.create_material("material_aniso", (1E-10, 1E-11, 1E-11), 0.5, 1.0)
model.create_material("material_racetrack", (1E-7, 1E-7, 1E-7), 0.5, 0.5)
rosette_ramp = liz.Rosette(model.get_node_by_id(12).coords, model.get_node_by_id(13).coords)
model.assign_material("material_iso", "Lshape")
model.assign_material("material_aniso", "ramp", rosette_ramp)
model.assign_material("material_racetrack", "racetrack")

model.create_pressure_inlet("inlet", 1E+05)
model.assign_inlet("inlet", "inlet")

model.initialise_solver()
solution = model.solve()
model.save_results(solution, "Complex_rotated")

Solution visualisation

Load up the file Complex_rotated_RES.xdmf into Paraview to visualise the results:

../_images/complex_t1.png ../_images/complex_t2.png ../_images/complex_t3.png

Observing the fill pattern we see that the flow front speeds up in the racetrack (a), fills gradually the L-shape (b) and finally rotates its orientation as it traverses the ramp because of the anisotropy (c).