Neko 1.99.1
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
fields_vectors_math.f90
Go to the documentation of this file.
1! This tutorial demonstrates how to work with fields and vectors in Neko. It
2! covers the following key points:
3!
4! - Initializing and working with `field_t` and `vector_t` types.
5! - Performing mathematical operations on fields and vectors, including:
6! - Using overloaded operators for basic operations.
7! - Accessing and modifying field data directly on the CPU.
8! - Using low-level math routines for device and CPU compatibility.
9! - Leveraging the `field_math` module for backend-agnostic computations.
10! - Demonstrating the lifecycle of user-defined objects:
11! - Initializing fields and vectors in `user_init_modules`.
12! - Performing custom calculations in `user_check`.
13! - Cleaning up allocated resources in `user_finalize_modules`.
14!
15! This tutorial highlights the flexibility of Neko's field and vector types,
16! as well as the tools provided for efficient and portable numerical
17! computations across different backends.
18module user
19 use neko
20 implicit none
21
22
23 ! One of the most important types in Neko is the field_t type. It is used to
24 ! represent the solution fields in the simulation.
25 type(field_t) :: my_field
26
27 ! A vector is essentially a 1D array that handles the storage and association
28 ! between data on the host and the device.
29 type(vector_t) :: vec
30
31contains
32
33 ! Register user-defined functions (see user_intf.f90)
34 subroutine user_setup(user)
35 type(user_t), intent(inout) :: user
36
37 user%startup => startup
38 user%initialize => initialize
39 user%compute => compute
40 user%finalize => finalize
41 end subroutine user_setup
42
43 ! We will use the user_startup routine to manipulate the end time.
44 subroutine startup(params)
45 type(json_file), intent(inout) :: params
46
47 call params%add("case.end_time", 0.0_rp)
48 end subroutine startup
49
50 ! The user_init_modules routine is called after all the objects used to run
51 ! the simulation are created. Thus, we can initialize user variables that,
52 ! for example, depend on the mesh or the fluid solver.
53 subroutine initialize(time)
54 type(time_state_t), intent(in) :: time
55
56 ! Like almost all other Neko types, the field_t has an init method that is
57 ! effectively a constructor. Typically, we use a dofmap_t object and a
58 ! a name to call the init method. The dofmap_t is usually fetched from the
59 ! solver, like a `fluid_scheme_t`. To do this, we make use of the special
60 ! neko_user_access object, which is a singleton that provides access to
61 ! various objects used in the simulation.
62 call my_field%init(neko_user_access%case%fluid%dm_Xh, "my_field")
63
64 ! The actual values of the field are stored in the x array or the x_d
65 ! pointer. The x_d pointer is used to access the data on the GPU. The x
66 ! array has a rank of 4, corresponding to GLL points in 3 dimensions, and
67 ! the number of elements in the mesh (i,j,k,e).
68
69 ! Some operators are overloaded for the field_t type. For example, we can
70 ! assign it to a scalar. At construction, the values are set to zero.
71 my_field = 1.0_rp
72
73
74 ! The situation for vector is similar, but we only need to provide the size
75 ! of the array we want in the constructor.
76 call vec%init(50)
77
78 vec = 1.0_rp
79
80 end subroutine initialize
81
82 ! This routine is run at the end of each time step. It is mean to hold
83 ! arbitrary calculations that the user wants to do.
84 subroutine compute(time)
85 type(time_state_t), intent(in) :: time
86 integer :: i
87
88 ! Let us consider doing some simple math on our field. Here we just add 1
89 ! to the value at each GLL point. Note that we use a linear index to loop
90 ! over the entire field, and use %x(i,1,1,1). This is very common in Neko,
91 ! and relies on contiguous memory layout. Sometimes you will see 4D arrays
92 ! passed to dummy arguments that are 1D, 2D, etc. This is possible for the
93 ! same reason.
94 do i = 1, my_field%size() ! <- Number of GLL points.
95 my_field%x(i,1,1,1) = my_field%x(i,1,1,1) * 2.0_rp
96 end do
97
98 ! There is an issue with the above code: it only works on the CPU. You can
99 ! not write GPU kernels as a user. Therefore, Neko has a whole range of
100 ! low-level math functions that can be used. You can find them in math.f90.
101 ! Corresponding routines for the GPU are in device_math.f90, and
102 ! field_math.f90 contains wrappers that dispatch correctly to either a CPU
103 ! or device routine based on the current backend. The names of the routines
104 ! are taken from Nek5000, so if you are familiar with that code, you will
105 ! find it easy to use.
106
107 ! A generic way to perform the computation above would be
108 if (neko_bcknd_device .eq. 1) then ! <- Check if we are on the device.
109 call device_cmult(my_field%x_d, 2.0_rp, my_field%size())
110 else
111 call cmult(my_field%x, 2.0_rp, my_field%size())
112 end if
113
114 ! Alternatively, (and more concisely) we can use the field_math module,
115 call field_cmult(my_field, 2.0_rp)
116
117 ! For vector_t there are no math wrappers, so the latter approach is the
118 ! one to go for. The vector_t does overload some operators though, so use
119 ! those when possible.
120
121 end subroutine compute
122
123 ! If you declare objects at module scope that need to be destroyed, you can
124 ! do it here. In our case it is the my_field field.
125 subroutine finalize(time)
126 type(time_state_t), intent(in) :: time
127
128 ! All types the allocate memory in Neko have a free method, which is a
129 ! destructor.
130 call my_field%free()
131
132 end subroutine finalize
133
134end module user
Master module.
Definition neko.f90:34
type(vector_t) vec
subroutine initialize(time)
subroutine user_setup(user)
subroutine compute(time)
subroutine startup(params)
type(field_t) my_field
subroutine finalize(time)