Tutorial 2: Create a 3D intestine connected with an inlet and and outlet
In this tutorial, a 3D small intestine (type 2, referred to as si in the code)
will be created which is connected with an inlet and an outlet (type 1).
Fluid (type 3) will also be filled with the intestine.
It is a part of my unpublished work.
The expected result is as follows:
What we will learn in this tutorial:
Creating 3D geometries:
CylinderSide,TorusSurface, andFilledCylinderPerforming operations:
mirrorandUnionAccessing
Geometryclass member:zs
Specify the parameters
Like Tutorial 1, let’s first import the necessary libraries and set up the simulation parameters:
1import geoparticle as gp
2from lammps import lammps
3import numpy as np
4
5rho_fluid = 993
6rho_wall = 1040
7l_pipe_hrz = 0.002
8l_pipe_vert = 0.004
9r_si = 0.002
10r_torus = 2 * r_si
11dl = 2e-4
12l_si = 0.02 - dl
Obtain all the particle coordinates
We will first create the inlet, then the intestine, and finally the outlet.
The outlet is a mirror of the inlet, which can be created by using the mirror operation.
The inlet is composed of a verticle pipe, a torus bend, and a horizontal pipe, with the same
radius r_si.
1pipe_in_vert = gp.CylinderSide(
2 r=r_si, # radius of the cylinder
3 l_axis=l_pipe_vert, # length along the cylinder axis
4 dl=dl, # particle spacing
5 axis='z' # cylinder axis direction
6).shift(x=-r_torus - l_pipe_hrz, z=r_torus)
7l_pipe_vert = pipe_in_vert.l_axis
8n_ring = pipe_in_vert.n_ring
Although we have defined the length of the vertical pipe as l_pipe_vert,
the actual length may be slightly different. So we update the variable l_pipe_vert here.
Now create the torus bend. But we should know how geoparticle defines the torus first. The torus is created by revolving a circle along a circular path. There two circles: the circle to be revolved and the path circle. The former is called the cross-section circle, whose radius is the minor radius \(r\) and the center angle is \(\theta\). The latter is called the path circle, whose radius is the major radius \(R\) and the center angle is \(\varphi\).
According to our dimension specification,
the minor radius is r_si, and the major radius is r_torus.
\(\varphi\) ranges from 180 to 270 degrees.
1torus_in = gp.TorusSurface(
2 r_minor=r_si, # minor radius
3 r_major=r_torus, # major radius
4 dl=dl, # particle spacing
5 n_ring=n_ring, # number of particle rings along minor circle
6 phi_range='(180,270)', # angle range along major circle
7 plane='XOZ' # plane where the major circle lies
8).shift(z=r_torus, x=-l_pipe_hrz)
Interval notation is used to define the range of \(\varphi\).
That is, [ and ] mean including the boundary,
while ( and ) mean excluding the boundary.
We exclude the boundaries here to avoid overlapping with the two pipes.
Alternatively, one can set phi_range='(180,270]',
reduce the length of the horizontal pipe by dl, and move it dl to the right accordingly.
Finally, create the horizontal pipe:
1pipe_in_hrz = gp.CylinderSide(
2 r=r_si, l_axis=l_pipe_hrz - dl, dl=dl, axis='x', name='pipe_in_hrz'
3).shift(x=-l_pipe_hrz)
Now we can assemble the three parts into an inlet:
1inlet = gp.Union((pipe_in_vert, torus_in, pipe_in_hrz))
Like the subtraction operation, there are three more ways to perform union:
inlet = pipe_in_vert.union(torus_in, pipe_in_hrz)inlet = pipe_in_vert + torus_in + pipe_in_hrz1inlet = pipe_in_vert.copy() 2inlet += torus_in 3inlet += pipe_in_hrz
Next, create the intestine.
In case the length of the intestine changed due to particle discretization, we update
the variable l_si here.
1si = gp.CylinderSide(r=r_si, l_axis=l_si, dl=dl, axis='x')
2l_si = si.l_axis
The outlet can be created by mirroring the inlet along the yz plane positioned at
the middle of the intestine:
1outlet = inlet.mirror(plane_name='YOZ', plane_pos=l_si / 2)
Finally, create the fluid that fills the intestine, which is a cylinder
with a radius of r_si - dl to avoid overlapping with the intestine wall.
1fluid = gp.FilledCylinder(r_si - dl, l_si, dl, axis='x', name='fluid')
Create particles in LAMMPS
Now we can create the particles in LAMMPS. This part is similar to Tutorial 1. I will skip the explanation here.
1lmp = lammps(cmdargs=['-screen', 'none', '-log', 'none'])
2# This buffer is between the geometry bound and the bound of the simulation box
3buffer_region = 0.1 * l_si
4xlo = -(l_pipe_hrz + r_torus + r_si) - buffer_region
5xhi = l_si + l_pipe_hrz + r_torus + r_si + buffer_region
6ylo = -r_si - buffer_region
7yhi = r_si + buffer_region
8zlo = -r_si - buffer_region
9zhi = l_pipe_vert + r_torus + buffer_region
10
11lmp.commands_string(f"""
12dimension 3
13atom_style rheo
14units si
15newton on
16boundary f f f
17comm_modify vel yes
18region simulation_box block {xlo} {xhi} {ylo} {yhi} {zlo} {zhi}
19create_box 3 simulation_box
20""")
21n_atoms_inlet = inlet.size
22lmp.create_atoms(
23 n_atoms_inlet, np.arange(n_atoms_inlet) + 1 + lmp.get_natoms(),
24 np.ones(n_atoms_inlet, dtype=int), inlet.flatten_coords)
25n_atoms_si = si.size
26lmp.create_atoms(
27 n_atoms_si, np.arange(n_atoms_si) + 1 + lmp.get_natoms(),
28 np.full(n_atoms_si, 2, dtype=int), si.flatten_coords)
29n_atoms_outlet = outlet.size
30lmp.create_atoms(
31 n_atoms_outlet, np.arange(n_atoms_outlet) + 1 + lmp.get_natoms(),
32 np.ones(n_atoms_outlet, dtype=int), outlet.flatten_coords)
33n_atoms_fluid = fluid.size
34lmp.create_atoms(
35 n_atoms_fluid, np.arange(n_atoms_fluid) + 1 + lmp.get_natoms(),
36 np.full(n_atoms_fluid, 3, dtype=int), fluid.flatten_coords)
37n_atoms_all = lmp.get_natoms()
38log_n_atom = f'n_atoms_inlet: {n_atoms_inlet},' \
39 f' n_atoms_si: {n_atoms_si},' \
40 f' n_atoms_outlet: {n_atoms_outlet},' \
41 f' n_atoms_fluid: {n_atoms_fluid},' \
42 f' n_atoms_all: {n_atoms_all}.'
43print(log_n_atom)
44
45lmp.commands_string(f"""
46mass * 1
47set group all rheo/rho {rho_fluid}
48""")
49
50filename = 'intestine3D'
51lmp.commands_string(f"""
52pair_style zero {dl * 2}
53pair_coeff * *
54neighbor {dl * 0.1} bin
55run 0
56write_data {filename}.data
57""")
58
59# check atom overlapping
60lmp.commands_string(f"""
61delete_atoms overlap {dl * 0.8} all all
62""")
63n_atoms_all_now = lmp.get_natoms()
64if n_atoms_all == n_atoms_all_now:
65 print('Congrats, no atoms are overlapped!')
66else:
67 raise RuntimeError(f'{n_atoms_all - n_atoms_all_now} atoms are overlapped!')