1+ !
2+ ! //////////////////////////////////////////////////////
3+ !
4+ ! Class cotaining routines for adapting time step based on Reinforcement Learning.
5+ ! -> The adaptation procedure is performed with a RL agent trined with a Value Iteration algorithm.
6+ ! -> The current implementation is compatible with OpenMP and MPI.
7+ !
8+ ! ////////////////////////////////////////////////////////////////////////
9+ !
10+ module AdaptiveTimeStepClass
11+ use SMConstants
12+ #ifdef NAVIERSTOKES
13+ use PhysicsStorage , only: CTD_IGNORE_MODE, flowIsNavierStokes
14+ use FluidData , only: thermodynamics
15+ #elif defined(MULTIPHASE)
16+ use PhysicsStorage , only: CTD_IGNORE_MODE, IMP
17+ #else
18+ use PhysicsStorage , only: CTD_IGNORE_MODE
19+ #endif
20+ use ElementClass
21+ use HexMeshClass
22+ USE DGSEMClass
23+ use FTValueDictionaryClass , only: FTValueDictionary
24+ use StorageClass
25+ use MPI_Process_Info
26+
27+ #ifdef _HAS_MPI_
28+ use mpi
29+ #endif
30+ implicit none
31+
32+ #include " Includes.h"
33+ private
34+ public adaptiveTimeStep_t
35+
36+ type :: adaptiveTimeStep_t
37+ real (kind= RP) :: min_dt = 1e-8_RP ! Minimum time step
38+ real (kind= RP) :: max_dt = 1e-2_RP ! Maximum time step
39+ real (kind= RP) :: b1 = 0.0 ! PID 1st parameter
40+ real (kind= RP) :: b2 = - 0.2 ! PID 2nd parameter
41+ real (kind= RP) :: b3 = 0.2 ! PID 3rd parameter
42+ real (kind= RP) :: k = 3.0 ! Order of the RK method (only RK3 is implemented)
43+ real (kind= RP) :: dt_eps(3 ) ! Epsilon for the PID controller
44+ logical :: constructed = .FALSE. ! If the adaptive time step is constructed
45+ real (kind= RP) :: dtAdaptationStep = 1e-3_RP ! Adaptation step
46+ real (kind= RP) :: lastAdaptationTime = 0.0_RP ! Last adaptation time
47+ integer :: error_variable ! 1:u, 2:v, 3:w, 4:rho*u, 5:rho*v, 6:rho*w, 7:p (only for Navier-Stokes)
48+ logical :: pAdapted = .FALSE.
49+
50+ contains
51+ procedure :: construct = > adaptiveTimeStep_Construct
52+ procedure :: destruct = > adaptiveTimeStep_Destruct
53+ procedure :: hasToAdapt = > adaptiveTimeStep_HasToAdapt
54+ procedure :: update = > adaptiveTimeStep_Update
55+ end type adaptiveTimeStep_t
56+
57+ contains
58+
59+ ! ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
60+ !
61+ ! -----------------------------------------------
62+ ! Routine for constructing the adaptive time step
63+ ! -----------------------------------------------
64+ subroutine adaptiveTimeStep_Construct (this , controlVariables , t0 )
65+ implicit none
66+ class(adaptiveTimeStep_t), intent (inout ) :: this
67+ type (FTValueDictionary) , intent (in ) :: controlVariables
68+ real (kind= RP) , intent (in ) :: t0
69+ ! ! ---------------
70+ this % dt_eps(1 ) = 1.0_RP
71+ this % dt_eps(2 ) = 1.0_RP
72+ this % dt_eps(3 ) = 1.0_RP
73+ this % constructed = .TRUE.
74+ this % lastAdaptationTime = t0
75+
76+ if (controlVariables % containsKey(" dt adaptation step" )) then
77+ this % dtAdaptationStep = controlVariables % doublePrecisionValueForKey(" dt adaptation step" )
78+ end if
79+
80+ if (controlVariables % containsKey(" minimum dt" )) then
81+ this % min_dt = controlVariables % doublePrecisionValueForKey(" minimum dt" )
82+ end if
83+
84+ if (controlVariables % containsKey(" maximum dt" )) then
85+ this % max_dt = controlVariables % doublePrecisionValueForKey(" maximum dt" )
86+ end if
87+ end subroutine adaptiveTimeStep_Construct
88+ !
89+ !
90+ ! -----------------------------------------------
91+ ! Subroutine for destructing the adaptive time step
92+ ! -----------------------------------------------
93+ subroutine adaptiveTimeStep_Destruct (this )
94+ implicit none
95+ class(adaptiveTimeStep_t), intent (inout ) :: this
96+ ! ! ---------------
97+ end subroutine adaptiveTimeStep_Destruct
98+ !
99+ ! -----------------------------------------------
100+ ! Subroutine for updating the adaptive time step
101+ ! -----------------------------------------------
102+ subroutine adaptiveTimeStep_Update (this , mesh , t , dt )
103+ implicit none
104+ class(adaptiveTimeStep_t) , intent (inout ) :: this
105+ class(HexMesh) , intent (in ) :: mesh
106+ real (kind= RP) , intent (in ) :: t
107+ real (kind= RP) , intent (inout ) :: dt
108+ ! ! ---------------
109+ ! Local variables
110+ ! ! ---------------
111+ integer :: eID, ierr, i
112+ real (kind= RP) :: dt_weight, sum_dt_weight, dt_lim, min_dt_lim, max_dt_lim
113+
114+ min_dt_lim = 0.5_RP ! Minimum limit for the time step
115+ max_dt_lim = 1.3_RP ! Maximum limit for the time step
116+
117+ this % lastAdaptationTime = t
118+ dt_weight = 0.0_RP
119+ sum_dt_weight = 0.0_RP
120+ ! $omp parallel shared(dt_weight, mesh, this)
121+ ! $omp do reduction(+:dt_weight) schedule(runtime)
122+ do eID = 1 , mesh % no_of_elements
123+ dt_weight = dt_weight + adaptiveTimeStep_ComputeWeights(this, mesh % elements(eID))
124+ end do
125+ ! $omp end do
126+ ! $omp end parallel
127+
128+ if ( MPI_Process % doMPIAction ) then
129+ #ifdef _HAS_MPI_
130+ call mpi_allreduce ( dt_weight, sum_dt_weight, 1 , MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD, ierr )
131+ #endif
132+ else
133+ sum_dt_weight = dt_weight
134+ end if
135+
136+ if (isnan(sum_dt_weight)) then
137+ this % dt_eps(3 ) = 1e-10_RP
138+ else
139+ this % dt_eps(3 ) = 1.0_RP / max (sqrt (sum_dt_weight / mesh % no_of_allElements), 1e-10_RP )
140+ end if
141+
142+ if ((this % dt_eps(1 ) == this % dt_eps(2 )) .and. (this % dt_eps(2 ) == 1.0_RP )) then
143+ this % dt_eps(1 ) = this % dt_eps(3 ) ! Initialize the first epsilon value
144+ this % dt_eps(2 ) = this % dt_eps(3 ) ! Initialize the second epsilon value
145+ else
146+ this % dt_eps(3 ) = min (this % dt_eps(3 ), this % dt_eps(2 ) * 10.0_RP ) ! Ensure that the third epsilon is not too large
147+ end if
148+
149+ if (this % dt_eps(2 ) > this % dt_eps(3 )) then
150+ if (this % pAdapted) then ! Right after p-Adaptation
151+ this % b3 = 0.15_RP
152+ this % b2 = - 0.15_RP
153+ this % pAdapted = .FALSE.
154+ else
155+ this % b3 = 0.2_RP
156+ this % b2 = - 0.2_RP
157+ end if
158+ else
159+ this % b3 = 0.2_RP
160+ if (this % dt_eps(2 ) < 1.0_RP ) then
161+ this % b2 = - 0.1_RP
162+ else if (this % dt_eps(2 ) / this % dt_eps(3 ) < 0.5_RP ) then
163+ if (this % pAdapted) then
164+ this % b3 = 0.15_RP
165+ this % b2 = max (- 0.15_RP * (1.0_RP + 0.01_RP * this % dt_eps(3 ) / this % dt_eps(2 )), - 0.2_RP )
166+ this % pAdapted = .FALSE.
167+ else
168+ this % b2 = max (- 0.2_RP * (1.0_RP + 0.01_RP * this % dt_eps(3 ) / this % dt_eps(2 )), - 0.25_RP )
169+ end if
170+ else
171+ this % b2 = - 0.2_RP
172+ end if
173+ end if
174+
175+ dt_lim = dt_limiter(this % dt_eps(3 ) ** (this % b3/ this % k) * &
176+ this % dt_eps(2 ) ** (this % b2/ this % k) * &
177+ this % dt_eps(1 ) ** (this % b1/ this % k))
178+
179+ if (dt_lim > 1.05_RP .or. dt_lim < 0.95_RP ) then
180+
181+ this % dt_eps(1 ) = this % dt_eps(2 )
182+ this % dt_eps(2 ) = this % dt_eps(3 )
183+
184+ if (dt_lim < 0.85_RP ) then
185+ dt_lim = dt_lim * 0.9_RP
186+ else if (dt_lim < 0.95_RP ) then
187+ dt_lim = dt_lim ** 1.2_RP
188+ else if (dt_lim < 1.0_RP ) then
189+ dt_lim = dt_lim
190+ else
191+ if (this % dt_eps(3 ) > 100.0_RP ) then
192+ dt_lim = dt_lim ** (1.1_RP + this % dt_eps(3 ) / 10000.0_RP )
193+ else
194+ dt_lim = dt_lim * (1.0_RP - 0.2_RP / this % dt_eps(3 ))
195+ end if
196+ end if
197+
198+ dt_lim = min (max (dt_lim, min_dt_lim), max_dt_lim) ! Limit the time step
199+ dt = dt * dt_lim
200+
201+ dt = min (max (dt, this % min_dt), this % max_dt) ! Ensure dt is not below the minimum value or above the maximum value
202+
203+ this % dtAdaptationStep = dt
204+
205+ end if ! end if dt_lim > 1.05_RP .or. dt_lim < 0.95_RP
206+
207+ end subroutine AdaptiveTimeStep_Update
208+
209+ function adaptiveTimeStep_ComputeWeights (this , e ) result(dt_weight)
210+ implicit none
211+ class(adaptiveTimeStep_t), intent (in ) :: this
212+ type (Element) , intent (in ) :: e
213+ real (kind= RP) :: dt_weight
214+ ! ! ---------------
215+ integer :: i, j, k, dir, Ndir
216+ integer :: Pxyz(3 )
217+ real (kind= RP) :: Q_error(0 :e % Nxyz(1 ), 0 :e % Nxyz(2 ), 0 :e % Nxyz(3 )), QLowRK_error(0 :e % Nxyz(1 ), 0 :e % Nxyz(2 ), 0 :e % Nxyz(3 ))
218+
219+ #ifdef FLOW
220+ ! Initialization of P
221+ Pxyz = e % Nxyz
222+
223+ Ndir = 7 ! Number of available error variables
224+
225+ dt_weight = 0.0_RP
226+
227+ ! Select the error variable
228+ do i = 0 , Pxyz(3 )
229+ do j = 0 , Pxyz(2 )
230+ do k = 0 , Pxyz(1 )
231+ do dir = 1 , Ndir
232+ if (mod (this % error_variable, 3 ) == mod (dir, 3 ) .and. dir <= 3 ) then
233+ if (this % error_variable <= 6 ) then
234+ Q_error(k, j, i) = e % storage % Q(dir+1 , k, j, i)
235+ QLowRK_error(k, j, i) = e % storage % QLowRK(dir+1 , k, j, i)
236+ if (this % error_variable <= 3 ) then
237+ Q_error(k, j, i) = Q_error(k, j, i) / e % storage % Q(1 , k, j, i)
238+ QLowRK_error(k, j, i) = QLowRK_error(k, j, i) / e % storage % QLowRK(1 , k, j, i)
239+ end if
240+ else if (this % error_variable == 7 ) then
241+ #ifdef NAVIERSTOKES
242+ ! Pressure
243+ Q_error(k, j, i) = thermodynamics % gammaMinus1* (e % storage % Q(5 ,k,j,i) - 0.5_RP * (e % storage % Q(2 ,k,j,i)** 2 + e % storage % Q(3 ,k,j,i)** 2 + e % storage % Q(4 ,k,j,i)** 2 )/ e % storage % Q(1 ,k,j,i))
244+ QLowRK_error(k, j, i) = thermodynamics % gammaMinus1* (e % storage % QLowRK(5 ,k,j,i) - 0.5_RP * (e % storage % QLowRK(2 ,k,j,i)** 2 + e % storage % QLowRK(3 ,k,j,i)** 2 + e % storage % QLowRK(4 ,k,j,i)** 2 )/ e % storage % QLowRK(1 ,k,j,i))
245+ #elif defined(MULTIPHASE)
246+ ! Pressure
247+ Q_error(k, j, i) = e % storage % QNS(IMP,k,j,i)
248+ QLowRK_error(k, j, i) = e % storage % QLowRK(IMP,k,j,i)
249+ #endif
250+ end if
251+ end if
252+ end do
253+ dt_weight = dt_weight + (Q_error(k, j, i) - QLowRK_error(k, j, i))** 2.0_RP
254+ end do
255+ end do
256+ end do
257+
258+ dt_weight = dt_weight / (e % storage % sensor)** 2.0_RP
259+ dt_weight = dt_weight / ((Pxyz(1 )+ 1 ) * (Pxyz(2 )+ 1 ) * (Pxyz(3 )+ 1 )) ! Average over all Gauss points
260+ #endif
261+ end function adaptiveTimeStep_ComputeWeights
262+
263+ subroutine adaptiveTimeStep_HasToAdapt (this , t , dt , dtHasToAdapt )
264+ implicit none
265+ class(adaptiveTimeStep_t), intent (in ) :: this
266+ real (kind= RP), intent (in ) :: t, dt
267+ logical , intent (out ) :: dtHasToAdapt
268+ ! ! ---------------
269+ dtHasToAdapt = (t - this % lastAdaptationTime + dt) >= this % dtAdaptationStep
270+
271+ end subroutine adaptiveTimeStep_HasToAdapt
272+
273+ function dt_limiter (a ) result(dt)
274+ implicit none
275+ real (kind= RP), intent (in ) :: a
276+ real (kind= RP) :: dt
277+ ! ! ---------------
278+ dt = 1.0_RP + atan (a-1.0_RP )
279+ end function dt_limiter
280+ end module AdaptiveTimeStepClass
0 commit comments