1 #include <petsc/private/tsimpl.h> /*I "petscts.h" I*/ 2 3 /* 4 Fills array sign[] with signs of array f[]. If abs(f[i]) < vtol[i], the zero sign is taken. 5 All arrays should have length 'nev' 6 */ 7 static inline void TSEventCalcSigns(PetscInt nev, const PetscReal *f, const PetscReal *vtol, PetscInt *sign) 8 { 9 for (PetscInt i = 0; i < nev; i++) { 10 if (PetscAbsReal(f[i]) < vtol[i]) sign[i] = 0; 11 else sign[i] = PetscSign(f[i]); 12 } 13 } 14 15 /* 16 TSEventInitialize - Initializes TSEvent for TSSolve 17 */ 18 PetscErrorCode TSEventInitialize(TSEvent event, TS ts, PetscReal t, Vec U) 19 { 20 PetscFunctionBegin; 21 if (!event) PetscFunctionReturn(PETSC_SUCCESS); 22 PetscAssertPointer(event, 1); 23 PetscValidHeaderSpecific(ts, TS_CLASSID, 2); 24 PetscValidHeaderSpecific(U, VEC_CLASSID, 4); 25 event->ptime_prev = t; 26 event->iterctr = 0; 27 event->processing = PETSC_FALSE; 28 event->revisit_right = PETSC_FALSE; 29 PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue_prev, event->ctx)); 30 TSEventCalcSigns(event->nevents, event->fvalue_prev, event->vtol, event->fsign_prev); // by this time event->vtol should have been defined 31 PetscFunctionReturn(PETSC_SUCCESS); 32 } 33 34 PetscErrorCode TSEventDestroy(TSEvent *event) 35 { 36 PetscFunctionBegin; 37 PetscAssertPointer(event, 1); 38 if (!*event) PetscFunctionReturn(PETSC_SUCCESS); 39 if (--(*event)->refct > 0) { 40 *event = NULL; 41 PetscFunctionReturn(PETSC_SUCCESS); 42 } 43 44 PetscCall(PetscFree((*event)->fvalue_prev)); 45 PetscCall(PetscFree((*event)->fvalue)); 46 PetscCall(PetscFree((*event)->fvalue_right)); 47 PetscCall(PetscFree((*event)->fsign_prev)); 48 PetscCall(PetscFree((*event)->fsign)); 49 PetscCall(PetscFree((*event)->fsign_right)); 50 PetscCall(PetscFree((*event)->side)); 51 PetscCall(PetscFree((*event)->side_prev)); 52 PetscCall(PetscFree((*event)->justrefined_AB)); 53 PetscCall(PetscFree((*event)->gamma_AB)); 54 PetscCall(PetscFree((*event)->direction)); 55 PetscCall(PetscFree((*event)->terminate)); 56 PetscCall(PetscFree((*event)->events_zero)); 57 PetscCall(PetscFree((*event)->vtol)); 58 59 for (PetscInt i = 0; i < (*event)->recsize; i++) PetscCall(PetscFree((*event)->recorder.eventidx[i])); 60 PetscCall(PetscFree((*event)->recorder.eventidx)); 61 PetscCall(PetscFree((*event)->recorder.nevents)); 62 PetscCall(PetscFree((*event)->recorder.stepnum)); 63 PetscCall(PetscFree((*event)->recorder.time)); 64 65 PetscCall(PetscViewerDestroy(&(*event)->monitor)); 66 PetscCall(PetscFree(*event)); 67 PetscFunctionReturn(PETSC_SUCCESS); 68 } 69 70 /*@ 71 TSSetPostEventStep - Set the time step to use immediately following the event 72 73 Logically Collective 74 75 Input Parameters: 76 + ts - time integration context 77 - dt - post event step 78 79 Options Database Key: 80 . -ts_event_post_event_step <dt> - time step after the event; zero value - to keep using previous time steps 81 82 Level: advanced 83 84 Notes: 85 `TSSetPostEventStep()` allows one to set a time step that is used immediately following an event. 86 If a positive real number is specified, it will be applied as is. 87 However, if `TSAdapt` is allowed to interfere, a large 'dt' may get truncated, resulting in a smaller actual post-event step. 88 89 The post-event time step should be selected based on the post-event dynamics. 90 If the dynamics are stiff, or a significant jump in the equations or the state vector has taken place at the event, 91 a conservative (small) step should be employed. If not, then a larger time step may be appropriate. 92 93 In the latter case, instead of explicitly setting the post-event time step, 94 the user may also choose a special option of keeping the time steps used before the event, which is a sort of 'petsc-decide' 95 strategy. For this, use special value 0. It will signal the TS to directly step to the time point it planned to visit 96 prior to the event interval detection. E.g. if a step t0 -> t1 was planned originally, and an event 'te' occurred, t0 < te < t1, 97 then after the event the TS will step: te -> t1. 98 Moreover, in this situation the originally planned subsequent step t1 -> t2 will also be preserved. 99 100 This function can be called not only in the initial setup, but also inside the `postevent()` callback set with `TSSetEventHandler()`, 101 affecting the post-event step for the current event, and the subsequent ones. 102 So, the strategy of the post-event time step definition can be adjusted on the fly. 103 Even if several events have been triggered in the given time point, only a single postevent handler is invoked, 104 and the user is to determine what post-event time step is more appropriate in this situation. 105 106 By default (on `TSSetEventHandler()` call), the post-event time step is set equal to the (initial) `TS` time step. 107 108 .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()` 109 @*/ 110 PetscErrorCode TSSetPostEventStep(TS ts, PetscReal dt) 111 { 112 PetscFunctionBegin; 113 ts->event->timestep_postevent = dt; 114 PetscFunctionReturn(PETSC_SUCCESS); 115 } 116 117 /*@ 118 TSSetPostEventIntervalStep - Set the time-step used immediately following an event interval 119 120 Logically Collective 121 122 Input Parameters: 123 + ts - time integration context 124 - dt - post-event interval step 125 126 Options Database Key: 127 . -ts_event_post_eventinterval_step <dt> - time-step after event interval 128 129 Level: advanced 130 131 Notes: 132 This function is deprecated, and its invocation will throw a runtime error. Use `TSSetPostEventStep()`. 133 134 .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()` 135 @*/ 136 PetscErrorCode TSSetPostEventIntervalStep(TS ts, PetscReal dt) 137 { 138 PetscFunctionBegin; 139 //ts->event->timestep_posteventinterval = dt; 140 /* 141 This deprecated function is set to always throw a runtime error. Attempting to reproduce its original behaviour, 142 i.e. setting the second (not the first) step after event, would break the logic of the TSEventHandler new code. 143 */ 144 PetscCheck(PETSC_FALSE, PetscObjectComm((PetscObject)ts), PETSC_ERR_SUP, "TSSetPostEventIntervalStep() is deprecated --> TSSetPostEventStep() should be used instead"); 145 PetscFunctionReturn(PETSC_SUCCESS); 146 } 147 148 /*@ 149 TSSetEventTolerances - Set tolerances for event (indicator function) zero crossings 150 151 Logically Collective 152 153 Input Parameters: 154 + ts - time integration context 155 . tol - tolerance, `PETSC_DECIDE` or `PETSC_DEFAULT` to leave the current value 156 - vtol - array of tolerances or `NULL`, used in preference to `tol` if present 157 158 Options Database Key: 159 . -ts_event_tol <tol> - tolerance for event (indicator function) zero crossing 160 161 Level: beginner 162 163 Notes: 164 One must call `TSSetEventHandler()` before setting the tolerances. 165 166 The size of `vtol` should be equal to the number of events on the given process. 167 168 This function can be also called from the `postevent()` callback set with `TSSetEventHandler()`, 169 to adjust the tolerances on the fly. 170 171 .seealso: [](ch_ts), `TS`, `TSEvent`, `TSSetEventHandler()` 172 @*/ 173 PetscErrorCode TSSetEventTolerances(TS ts, PetscReal tol, PetscReal vtol[]) 174 { 175 TSEvent event; 176 177 PetscFunctionBegin; 178 PetscValidHeaderSpecific(ts, TS_CLASSID, 1); 179 if (vtol) PetscAssertPointer(vtol, 3); 180 PetscCheck(ts->event, PetscObjectComm((PetscObject)ts), PETSC_ERR_USER, "Must set the events first by calling TSSetEventHandler()"); 181 182 event = ts->event; 183 if (vtol) { 184 for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = vtol[i]; 185 } else { 186 if (tol != (PetscReal)PETSC_DECIDE && tol != (PetscReal)PETSC_DEFAULT) { 187 for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = tol; 188 } 189 } 190 PetscFunctionReturn(PETSC_SUCCESS); 191 } 192 193 /*@C 194 TSSetEventHandler - Sets functions and parameters used for indicating events and handling them 195 196 Logically Collective 197 198 Input Parameters: 199 + ts - the `TS` context obtained from `TSCreate()` 200 . nevents - number of local events (i.e. managed by the given MPI process) 201 . direction - direction of zero crossing to be detected (one for each local event). 202 `-1` => zero crossing in negative direction, 203 `+1` => zero crossing in positive direction, `0` => both ways 204 . terminate - flag to indicate whether time stepping should be terminated after 205 an event is detected (one for each local event) 206 . indicator - callback defininig the user indicator functions whose sign changes (see `direction`) mark presence of the events 207 . postevent - [optional] user post-event callback; it can change the solution, ODE etc at the time of the event 208 - ctx - [optional] user-defined context for private data for the 209 `indicator()` and `postevent()` routines (use `NULL` if no 210 context is desired) 211 212 Calling sequence of `indicator`: 213 + ts - the `TS` context 214 . t - current time 215 . U - current solution 216 . fvalue - output array with values of local indicator functions (length == `nevents`) for time t and state-vector U 217 - ctx - the context passed as the final argument to `TSSetEventHandler()` 218 219 Calling sequence of `postevent`: 220 + ts - the `TS` context 221 . nevents_zero - number of triggered local events (whose indicator function is marked as crossing zero, and direction is appropriate) 222 . events_zero - indices of the triggered local events 223 . t - current time 224 . U - current solution 225 . forwardsolve - flag to indicate whether `TS` is doing a forward solve (`PETSC_TRUE`) or adjoint solve (`PETSC_FALSE`) 226 - ctx - the context passed as the final argument to `TSSetEventHandler()` 227 228 Options Database Keys: 229 + -ts_event_tol <tol> - tolerance for zero crossing check of indicator functions 230 . -ts_event_monitor - print choices made by event handler 231 . -ts_event_recorder_initial_size <recsize> - initial size of event recorder 232 . -ts_event_post_event_step <dt> - time step after event 233 - -ts_event_dt_min <dt> - minimum time step considered for TSEvent 234 235 Level: intermediate 236 237 Notes: 238 The indicator functions should be defined in the `indicator` callback using the components of solution `U` and/or time `t`. 239 Note that `U` is `PetscScalar`-valued, and the indicator functions are `PetscReal`-valued. It is the user's responsibility to 240 properly handle this difference, e.g. by applying `PetscRealPart()` or other appropriate conversion means. 241 242 The full set of events is distributed (by the user design) across MPI processes, with each process defining its own local sub-set of events. 243 However, the `postevent()` callback invocation is performed synchronously on all processes, including 244 those processes which have not currently triggered any events. 245 246 .seealso: [](ch_ts), `TSEvent`, `TSCreate()`, `TSSetTimeStep()`, `TSSetConvergedReason()` 247 @*/ 248 PetscErrorCode TSSetEventHandler(TS ts, PetscInt nevents, PetscInt direction[], PetscBool terminate[], PetscErrorCode (*indicator)(TS ts, PetscReal t, Vec U, PetscReal fvalue[], void *ctx), PetscErrorCode (*postevent)(TS ts, PetscInt nevents_zero, PetscInt events_zero[], PetscReal t, Vec U, PetscBool forwardsolve, void *ctx), void *ctx) 249 { 250 TSAdapt adapt; 251 PetscReal hmin; 252 TSEvent event; 253 PetscBool flg; 254 #if defined PETSC_USE_REAL_SINGLE 255 PetscReal tol = 1e-4; 256 #else 257 PetscReal tol = 1e-6; 258 #endif 259 260 PetscFunctionBegin; 261 PetscValidHeaderSpecific(ts, TS_CLASSID, 1); 262 if (nevents) { 263 PetscAssertPointer(direction, 3); 264 PetscAssertPointer(terminate, 4); 265 } 266 PetscCall(PetscNew(&event)); 267 PetscCall(PetscMalloc1(nevents, &event->fvalue_prev)); 268 PetscCall(PetscMalloc1(nevents, &event->fvalue)); 269 PetscCall(PetscMalloc1(nevents, &event->fvalue_right)); 270 PetscCall(PetscMalloc1(nevents, &event->fsign_prev)); 271 PetscCall(PetscMalloc1(nevents, &event->fsign)); 272 PetscCall(PetscMalloc1(nevents, &event->fsign_right)); 273 PetscCall(PetscMalloc1(nevents, &event->side)); 274 PetscCall(PetscMalloc1(nevents, &event->side_prev)); 275 PetscCall(PetscMalloc1(nevents, &event->justrefined_AB)); 276 PetscCall(PetscMalloc1(nevents, &event->gamma_AB)); 277 PetscCall(PetscMalloc1(nevents, &event->direction)); 278 PetscCall(PetscMalloc1(nevents, &event->terminate)); 279 PetscCall(PetscMalloc1(nevents, &event->events_zero)); 280 PetscCall(PetscMalloc1(nevents, &event->vtol)); 281 for (PetscInt i = 0; i < nevents; i++) { 282 event->direction[i] = direction[i]; 283 event->terminate[i] = terminate[i]; 284 event->justrefined_AB[i] = PETSC_FALSE; 285 event->gamma_AB[i] = 1; 286 event->side[i] = 2; 287 event->side_prev[i] = 0; 288 } 289 event->iterctr = 0; 290 event->processing = PETSC_FALSE; 291 event->revisit_right = PETSC_FALSE; 292 event->nevents = nevents; 293 event->indicator = indicator; 294 event->postevent = postevent; 295 event->ctx = ctx; 296 event->timestep_postevent = ts->time_step; 297 PetscCall(TSGetAdapt(ts, &adapt)); 298 PetscCall(TSAdaptGetStepLimits(adapt, &hmin, NULL)); 299 event->timestep_min = hmin; 300 301 event->recsize = 8; /* Initial size of the recorder */ 302 PetscOptionsBegin(((PetscObject)ts)->comm, ((PetscObject)ts)->prefix, "TS Event options", "TS"); 303 { 304 PetscCall(PetscOptionsReal("-ts_event_tol", "Tolerance for zero crossing check of indicator functions", "TSSetEventTolerances", tol, &tol, NULL)); 305 PetscCall(PetscOptionsName("-ts_event_monitor", "Print choices made by event handler", "", &flg)); 306 PetscCall(PetscOptionsInt("-ts_event_recorder_initial_size", "Initial size of event recorder", "", event->recsize, &event->recsize, NULL)); 307 PetscCall(PetscOptionsReal("-ts_event_post_event_step", "Time step after event", "", event->timestep_postevent, &event->timestep_postevent, NULL)); 308 PetscCall(PetscOptionsDeprecated("-ts_event_post_eventinterval_step", NULL, "3.20", "Use -ts_event_post_event_step")); 309 PetscCall(PetscOptionsReal("-ts_event_dt_min", "Minimum time step considered for TSEvent", "", event->timestep_min, &event->timestep_min, NULL)); 310 } 311 PetscOptionsEnd(); 312 313 PetscCall(PetscMalloc1(event->recsize, &event->recorder.time)); 314 PetscCall(PetscMalloc1(event->recsize, &event->recorder.stepnum)); 315 PetscCall(PetscMalloc1(event->recsize, &event->recorder.nevents)); 316 PetscCall(PetscMalloc1(event->recsize, &event->recorder.eventidx)); 317 for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscMalloc1(event->nevents, &event->recorder.eventidx[i])); 318 /* Initialize the event recorder */ 319 event->recorder.ctr = 0; 320 321 for (PetscInt i = 0; i < event->nevents; i++) event->vtol[i] = tol; 322 if (flg) PetscCall(PetscViewerASCIIOpen(PETSC_COMM_SELF, "stdout", &event->monitor)); 323 324 PetscCall(TSEventDestroy(&ts->event)); 325 ts->event = event; 326 ts->event->refct = 1; 327 PetscFunctionReturn(PETSC_SUCCESS); 328 } 329 330 /* 331 TSEventRecorderResize - Resizes (2X) the event recorder arrays whenever the recording limit (event->recsize) 332 is reached. 333 */ 334 static PetscErrorCode TSEventRecorderResize(TSEvent event) 335 { 336 PetscReal *time; 337 PetscInt *stepnum, *nevents; 338 PetscInt **eventidx; 339 PetscInt fact = 2; 340 341 PetscFunctionBegin; 342 /* Create larger arrays */ 343 PetscCall(PetscMalloc1(fact * event->recsize, &time)); 344 PetscCall(PetscMalloc1(fact * event->recsize, &stepnum)); 345 PetscCall(PetscMalloc1(fact * event->recsize, &nevents)); 346 PetscCall(PetscMalloc1(fact * event->recsize, &eventidx)); 347 for (PetscInt i = 0; i < fact * event->recsize; i++) PetscCall(PetscMalloc1(event->nevents, &eventidx[i])); 348 349 /* Copy over data */ 350 PetscCall(PetscArraycpy(time, event->recorder.time, event->recsize)); 351 PetscCall(PetscArraycpy(stepnum, event->recorder.stepnum, event->recsize)); 352 PetscCall(PetscArraycpy(nevents, event->recorder.nevents, event->recsize)); 353 for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscArraycpy(eventidx[i], event->recorder.eventidx[i], event->recorder.nevents[i])); 354 355 /* Destroy old arrays */ 356 for (PetscInt i = 0; i < event->recsize; i++) PetscCall(PetscFree(event->recorder.eventidx[i])); 357 PetscCall(PetscFree(event->recorder.eventidx)); 358 PetscCall(PetscFree(event->recorder.nevents)); 359 PetscCall(PetscFree(event->recorder.stepnum)); 360 PetscCall(PetscFree(event->recorder.time)); 361 362 /* Set pointers */ 363 event->recorder.time = time; 364 event->recorder.stepnum = stepnum; 365 event->recorder.nevents = nevents; 366 event->recorder.eventidx = eventidx; 367 368 /* Update the size */ 369 event->recsize *= fact; 370 PetscFunctionReturn(PETSC_SUCCESS); 371 } 372 373 /* 374 Helper routine to handle user postevents and recording 375 */ 376 static PetscErrorCode TSPostEvent(TS ts, PetscReal t, Vec U) 377 { 378 TSEvent event = ts->event; 379 PetscBool restart = PETSC_FALSE; 380 PetscBool terminate = PETSC_FALSE; 381 PetscBool statechanged = PETSC_FALSE; 382 PetscInt ctr, stepnum; 383 PetscBool inflag[3], outflag[3]; 384 PetscBool forwardsolve = PETSC_TRUE; // Flag indicating that TS is doing a forward solve 385 386 PetscFunctionBegin; 387 if (event->postevent) { 388 PetscObjectState state_prev, state_post; 389 PetscCall(PetscObjectStateGet((PetscObject)U, &state_prev)); 390 PetscCallBack("TSEvent post-event processing", (*event->postevent)(ts, event->nevents_zero, event->events_zero, t, U, forwardsolve, event->ctx)); // TODO update 'restart' here? 391 PetscCall(PetscObjectStateGet((PetscObject)U, &state_post)); 392 if (state_prev != state_post) { 393 restart = PETSC_TRUE; 394 statechanged = PETSC_TRUE; 395 } 396 } 397 398 // Handle termination events and step restart 399 for (PetscInt i = 0; i < event->nevents_zero; i++) 400 if (event->terminate[event->events_zero[i]]) terminate = PETSC_TRUE; 401 inflag[0] = restart; 402 inflag[1] = terminate; 403 inflag[2] = statechanged; 404 PetscCall(MPIU_Allreduce(inflag, outflag, 3, MPIU_BOOL, MPI_LOR, ((PetscObject)ts)->comm)); 405 restart = outflag[0]; 406 terminate = outflag[1]; 407 statechanged = outflag[2]; 408 if (restart) PetscCall(TSRestartStep(ts)); 409 if (terminate) PetscCall(TSSetConvergedReason(ts, TS_CONVERGED_EVENT)); 410 411 /* 412 Recalculate the indicator functions and signs if the state has been changed by the user postevent callback. 413 Note! If the state HAS NOT changed, the existing event->fsign (equal to zero) is kept, which: 414 - might have been defined using the previous (now-possibly-overridden) event->vtol, 415 - might have been set to zero on reaching a small time step rather than using the vtol criterion. 416 This will enforce keeping event->fsign = 0 where the zero-crossings were actually marked, 417 resulting in a more consistent behaviour of fsign's. 418 */ 419 if (statechanged) { 420 if (event->monitor) PetscCall(PetscPrintf(((PetscObject)ts)->comm, "TSEvent: at time %g the vector state has been changed by PostEvent, recalculating fvalues and signs\n", (double)t)); 421 PetscCall(VecLockReadPush(U)); 422 PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue, event->ctx)); 423 PetscCall(VecLockReadPop(U)); 424 TSEventCalcSigns(event->nevents, event->fvalue, event->vtol, event->fsign); // note, event->vtol might have been changed by the postevent() 425 } 426 427 // Record the event in the event recorder 428 PetscCall(TSGetStepNumber(ts, &stepnum)); 429 ctr = event->recorder.ctr; 430 if (ctr == event->recsize) PetscCall(TSEventRecorderResize(event)); 431 event->recorder.time[ctr] = t; 432 event->recorder.stepnum[ctr] = stepnum; 433 event->recorder.nevents[ctr] = event->nevents_zero; 434 for (PetscInt i = 0; i < event->nevents_zero; i++) event->recorder.eventidx[ctr][i] = event->events_zero[i]; 435 event->recorder.ctr++; 436 PetscFunctionReturn(PETSC_SUCCESS); 437 } 438 439 // PetscClangLinter pragma disable: -fdoc-sowing-chars 440 /* 441 (modified) Anderson-Bjorck variant of regula falsi method, refines [tleft, t] or [t, tright] based on 'side' (-1 or +1). 442 The scaling parameter is defined based on the 'justrefined' flag, the history of repeats of 'side', and the threshold. 443 To escape certain failure modes, the algorithm may drift towards the bisection rule. 444 The value pointed to by 'side_prev' gets updated. 445 This function returns the new time step. 446 447 The underlying pure Anderson-Bjorck algorithm was taken as described in 448 J.M. Fernandez-Diaz, C.O. Menendez-Perez "A common framework for modified Regula Falsi methods and new methods of this kind", 2023. 449 The modifications subsequently introduced have little effect on the behaviour for simple cases requiring only a few iterations 450 (some minor convergence slowdown may take place though), but the effect becomes more pronounced for tough cases requiring many iterations. 451 For the latter situation the speed-up may be order(s) of magnitude compared to the classical Anderson-Bjorck. 452 The modifications (the threshold trick, and the drift towards bisection) were tested and tweaked 453 based on a number of test functions from the mentioned paper. 454 */ 455 static inline PetscReal RefineAndersonBjorck(PetscReal tleft, PetscReal t, PetscReal tright, PetscReal fleft, PetscReal f, PetscReal fright, PetscInt side, PetscInt *side_prev, PetscBool justrefined, PetscReal *gamma) 456 { 457 PetscReal new_dt, scal = 1.0, scalB = 1.0, threshold = 0.0, power; 458 PetscInt reps = 0; 459 const PetscInt REPS_CAP = 8; // an upper bound to be imposed on 'reps' (set to 8, somewhat arbitrary number, found after some tweaking) 460 461 // Preparations 462 if (justrefined) { 463 if (*side_prev * side > 0) *side_prev += side; // the side keeps repeating -> increment the side counter (-ve or +ve) 464 else *side_prev = side; // reset the counter 465 reps = PetscMin(*side_prev * side, REPS_CAP); // the length of the recent side-repeat series, including the current 'side' 466 threshold = PetscPowReal(0.5, reps) * 0.1; // ad-hoc strategy for threshold calculation (involved some tweaking) 467 } else *side_prev = side; // initial reset of the counter 468 469 // Time step calculation 470 if (side == -1) { 471 if (justrefined && fright != 0.0 && fleft != 0.0) { 472 scal = (fright - f) / fright; 473 scalB = -f / fleft; 474 } 475 } else { // must be side == +1 476 if (justrefined && fleft != 0.0 && fright != 0.0) { 477 scal = (fleft - f) / fleft; 478 scalB = -f / fright; 479 } 480 } 481 482 if (scal < threshold) scal = 0.5; 483 if (reps > 1) *gamma *= scal; // side did not switch since the last time, accumulate gamma 484 else *gamma = 1.0; // side switched -> reset gamma 485 power = PetscMax(0.0, (reps - 2.0) / (REPS_CAP - 2.0)); 486 scal = PetscPowReal(scalB / *gamma, power) * (*gamma); // mix the Anderson-Bjorck scaling and Bisection scaling 487 488 if (side == -1) new_dt = scal * fleft / (scal * fleft - f) * (t - tleft); 489 else new_dt = f / (f - scal * fright) * (tright - t); 490 /* 491 In tough cases (e.g. a polynomial of high order), there is a failure mode for the standard Anderson-Bjorck, 492 when the new proposed point jumps from one end-point of the bracket to the other, however the bracket 493 is contracting very slowly. A larger threshold for 'scal' prevents entering this mode. 494 On the other hand, if the iteration gets stuck near one end-point of the bracket, and the 'side' does not switch for a while, 495 the 'scal' drifts towards the bisection approach (via scalB), ensuring stable convergence. 496 */ 497 return new_dt; 498 } 499 500 /* 501 Checks if the current point (t) is the zero-crossing location, based on the indicator function signs and direction[]: 502 - using the dt_min criterion, 503 - using the vtol criterion. 504 The situation (fsign_prev, fsign) = (0, 0) is treated as staying in the near-zero-zone of the previous zero-crossing, 505 and is not marked as a new zero-crossing. 506 This function may update event->side[]. 507 */ 508 static PetscErrorCode TSEventTestZero(TS ts, PetscReal t) 509 { 510 TSEvent event = ts->event; 511 512 PetscFunctionBegin; 513 for (PetscInt i = 0; i < event->nevents; i++) { 514 const PetscBool bracket_is_left = (event->fsign_prev[i] * event->fsign[i] < 0 && event->fsign[i] * event->direction[i] >= 0) ? PETSC_TRUE : PETSC_FALSE; 515 516 if (bracket_is_left && ((t - event->ptime_prev <= event->timestep_min) || event->revisit_right)) event->side[i] = 0; // mark zero-crossing from dt_min; 'bracket_is_left' accounts for direction 517 if (event->fsign[i] == 0 && event->fsign_prev[i] != 0 && event->fsign_prev[i] * event->direction[i] <= 0) event->side[i] = 0; // mark zero-crossing from vtol 518 } 519 PetscFunctionReturn(PETSC_SUCCESS); 520 } 521 522 /* 523 Checks if [fleft, f] or [f, fright] are 'brackets', i.e. intervals with the sign change, satisfying the 'direction'. 524 The right interval is only checked if iterctr > 0 (i.e. Anderson-Bjorck refinement has started). 525 The intervals like [0, x] and [x, 0] are not counted as brackets, i.e. intervals with the sign change. 526 The function returns the 'side' value: -1 (left, or both are brackets), +1 (only right one), +2 (neither). 527 */ 528 static inline PetscInt TSEventTestBracket(PetscInt fsign_left, PetscInt fsign, PetscInt fsign_right, PetscInt direction, PetscInt iterctr) 529 { 530 PetscInt side = 2; 531 if (fsign_left * fsign < 0 && fsign * direction >= 0) side = -1; 532 if (side != -1 && iterctr > 0 && fsign * fsign_right < 0 && fsign_right * direction >= 0) side = 1; 533 return side; 534 } 535 536 /* 537 Caps the time steps, accounting for time span points. 538 It uses 'event->timestep_cache' as a time step to calculate the tolerance for tspan points detection. This 539 is done since the event resolution may result in significant time step refinement, and we don't use these small steps for tolerances. 540 To enhance the consistency of tspan points detection, tolerance 'tspan->worktol' is reused later in the TSSolve iteration. 541 If a user-defined step is cut by this function, the input uncut step is saved to adapt->dt_span_cached. 542 Flag 'user_dt' indicates if the step was defined by user. 543 */ 544 static inline PetscReal TSEvent_dt_cap(TS ts, PetscReal t, PetscReal dt, PetscBool user_dt) 545 { 546 PetscReal res = dt; 547 if (ts->exact_final_time == TS_EXACTFINALTIME_MATCHSTEP) { 548 PetscReal maxdt = ts->max_time - t; // this may be overriden by tspan 549 PetscBool cut_made = PETSC_FALSE; 550 PetscReal eps = 10 * PETSC_MACHINE_EPSILON; 551 if (ts->tspan) { 552 PetscInt ctr = ts->tspan->spanctr; 553 PetscInt Ns = ts->tspan->num_span_times; 554 PetscReal *st = ts->tspan->span_times; 555 556 if (ts->tspan->worktol == 0) ts->tspan->worktol = ts->tspan->reltol * ts->event->timestep_cache + ts->tspan->abstol; // in case TSAdaptChoose() has not defined it 557 if (ctr < Ns && PetscIsCloseAtTol(t, st[ctr], ts->tspan->worktol, 0)) { // just hit a time span point 558 if (ctr + 1 < Ns) maxdt = st[ctr + 1] - t; // ok to use the next time span point 559 else maxdt = ts->max_time - t; // can't use the next time span point: they have finished 560 } else if (ctr < Ns) maxdt = st[ctr] - t; // haven't hit a time span point, use the nearest one 561 } 562 maxdt = PetscMin(maxdt, ts->max_time - t); 563 PetscCheck((maxdt > eps) || (PetscAbsReal(maxdt) <= eps && PetscIsCloseAtTol(t, ts->max_time, eps, 0)), PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state: bad maxdt in TSEvent_dt_cap()"); 564 565 if (PetscIsCloseAtTol(dt, maxdt, eps, 0)) res = maxdt; // no cut 566 else { 567 if (dt > maxdt) { 568 res = maxdt; // yes cut 569 cut_made = PETSC_TRUE; 570 } else res = dt; // no cut 571 } 572 if (ts->adapt && user_dt) { // only update dt_span_cached for the user-defined step 573 if (cut_made) ts->adapt->dt_span_cached = dt; 574 else ts->adapt->dt_span_cached = 0; 575 } 576 } 577 return res; 578 } 579 580 /* 581 Updates the left-end values 582 */ 583 static inline void TSEvent_update_left(TSEvent event, PetscReal t) 584 { 585 for (PetscInt i = 0; i < event->nevents; i++) { 586 event->fvalue_prev[i] = event->fvalue[i]; 587 event->fsign_prev[i] = event->fsign[i]; 588 } 589 event->ptime_prev = t; 590 } 591 592 /* 593 Updates the right-end values 594 */ 595 static inline void TSEvent_update_right(TSEvent event, PetscReal t) 596 { 597 for (PetscInt i = 0; i < event->nevents; i++) { 598 event->fvalue_right[i] = event->fvalue[i]; 599 event->fsign_right[i] = event->fsign[i]; 600 } 601 event->ptime_right = t; 602 } 603 604 /* 605 Updates the current values from the right-end values 606 */ 607 static inline PetscReal TSEvent_update_from_right(TSEvent event) 608 { 609 for (PetscInt i = 0; i < event->nevents; i++) { 610 event->fvalue[i] = event->fvalue_right[i]; 611 event->fsign[i] = event->fsign_right[i]; 612 } 613 return event->ptime_right; 614 } 615 616 // PetscClangLinter pragma disable: -fdoc-section-spacing 617 // PetscClangLinter pragma disable: -fdoc-section-header-unknown 618 // PetscClangLinter pragma disable: -fdoc-section-header-spelling 619 // PetscClangLinter pragma disable: -fdoc-section-header-missing 620 // PetscClangLinter pragma disable: -fdoc-section-header-fishy-header 621 // PetscClangLinter pragma disable: -fdoc-param-list-func-parameter-documentation 622 // PetscClangLinter pragma disable: -fdoc-synopsis-missing-description 623 // PetscClangLinter pragma disable: -fdoc-sowing-chars 624 /* 625 TSEventHandler - the main function to perform a single iteration of event detection. 626 627 Developer notes: 628 a) The 'event->iterctr > 0' is used as an indicator that Anderson-Bjorck refinement has started. 629 b) If event->iterctr == 0, then justrefined_AB[i] is always false. 630 c) The right-end quantities: ptime_right, fvalue_right[i] and fsign_right[i] are only guaranteed to be valid 631 for event->iterctr > 0. 632 d) If event->iterctr > 0, then event->processing is PETSC_TRUE; the opposite may not hold. 633 e) event->side[i] may take values: 0 <=> point t is a zero-crossing for indicator function i (via vtol/dt_min criterion); 634 -1/+1 <=> detected a bracket to the left/right of t for indicator function i; +2 <=> no brackets/zero-crossings. 635 f) The signs event->fsign[i] (with values 0/-1/+1) are calculated for each new point. Zero sign is set if the function value is 636 smaller than the tolerance. Besides, zero sign is enforced after marking a zero-crossing due to small bracket size criterion. 637 638 The intervals with the indicator function sign change (i.e. containing the potential zero-crossings) are called 'brackets'. 639 To find a zero-crossing, the algorithm first locates a bracket, and then sequentially subdivides it, generating a sequence 640 of brackets whose length tends to zero. The bracket subdivision involves the (modified) Anderson-Bjorck method. 641 642 Apart from the comments scattered throughout the code to clarify different lines and blocks, 643 a few tricky aspects of the algorithm (and the underlying reasoning) are discussed in detail below: 644 645 =Sign tracking= 646 When a zero-crossing is found, the sign variable (event->fsign[i]) is set to zero for the current point t. 647 This happens both for zero-crossings triggered via the vtol criterion, and those triggered via the dt_min 648 criterion. After the event, as the TS steps forward, the current sign values are handed over to event->fsign_prev[i]. 649 The recalculation of signs is avoided if possible: e.g. if a 'vtol' criterion resulted in a zero-crossing at point t, 650 but the subsequent call to postevent() handler decreased 'vtol', making the indicator function no longer "close to zero" 651 at point t, the fsign[i] will still consistently keep the zero value. This allows avoiding the erroneous duplication 652 of events: 653 654 E.g. consider a bracket [t0, t2], where f0 < 0, f2 > 0, which resulted in a zero-crossing t1 with f1 < 0, abs(f1) < vtol. 655 Suppose the postevent() handler changes vtol to vtol*, such that abs(f1) > vtol*. The TS makes a step t1 -> t3, where 656 again f1 < 0, f3 > 0, and the event handler will find a new event near t1, which is actually a duplication of the 657 original event at t1. The duplications are avoided by NOT counting the sign progressions 0 -> +1, or 0 -> -1 658 as brackets. Tracking (instead of recalculating) the sign values makes this procedure work more consistently. 659 660 The sign values are however recalculated if the postevent() callback has changed the current solution vector U 661 (such a change resets everything). 662 The sign value is also set to zero if the dt_min criterion has triggered the event. This allows the algorithm to 663 work consistently, irrespective of the type of criterion involved (vtol/dt_min). 664 665 =Event from min bracket= 666 When the event handler ends up with a bracket [t0, t1] with size <= dt_min, a zero crossing is reported at t1, 667 and never at t0. If such a bracket is discovered when TS is staying at t0, one more step forward (to t1) is necessary 668 to mark the found event. This is the situation of revisiting t1, which is described below (see Revisiting). 669 670 Why t0 is not reported as event location? Suppose it is, and let f0 < 0, f1 > 0. Also suppose that the 671 postevent() handler has slightly changed the solution U, so the sign at t0 is recalculated: it equals -1. As the TS steps 672 further: t0 -> t2, with sign0 == -1, and sign2 == +1, the event handler will locate the bracket [t0, t2], eventually 673 resolving a new event near t1, i.e. finding a duplicate event. 674 This situation is avoided by reporting the event at t1 in the first place. 675 676 =Revisiting= 677 When handling the situation with small bracket size, the TS solver may happen to visit the same point twice, 678 but with different results. 679 680 E.g. originally it discovered a bracket with sign change [t0, t10], and started resolving the zero-crossing, 681 visiting the points t1,...,t9 : t0 < t1 < ... < t9 < t10. Suppose that at t9 the algorithm discovers 682 that [t9, t10] is a bracket with the sign change it was looking for, and that |t10 - t9| is too small. 683 So point t10 should be revisited and marked as the zero crossing (by the minimum bracket size criterion). 684 On re-visiting t10, via the refined sequence of steps t0,...,t10, the TS solver may arrive at a solution U* 685 different from the solution U it found at t10 originally. Hence, the indicator functions at t10 may become different, 686 and the condition of the sign change, which existed originally, may disappear, breaking the logic of the algorithm. 687 688 To handle such (-=unlikely=-, but possible) situations, two strategies can be considered: 689 1) [not used here] Allow the brackets with sign change to disappear during iterations. The algorithm should be able 690 to cleanly exit the iteration and leave all the objects/variables/caches involved in a valid state. 691 2) [ADOPTED HERE!] On revisiting t10, the event handler reuses the indicator functions previously calculated for the 692 original solution U. This U may be less precise than U*, but this trick does not allow the algorithm logic to break down. 693 HOWEVER, the original U is not stored anywhere, it is essentially lost since the TS performed the rollback from it. 694 On revisiting t10, the updated solution U* will inevitably be found and used everywhere EXCEPT the current 695 indicator functions calculation, e.g. U* will be used in the postevent() handler call. Since t10 is the event location, 696 the appropriate indicator-function-signs will be enforced to be 0 (regardless if the solution was U or U*). 697 If the solution is then changed by the postevent(), the indicator-function-signs will be recalculated. 698 699 Whether the algorithm is revisiting a point in the current TSEventHandler() call is flagged by 'event->revisit_right'. 700 */ 701 PetscErrorCode TSEventHandler(TS ts) 702 { 703 TSEvent event; 704 PetscReal t, dt_next = 0.0; 705 Vec U; 706 PetscInt minsidein = 2, minsideout = 2; // minsideout is sync on all ranks 707 PetscBool finished = PETSC_FALSE; // should stay sync on all ranks 708 PetscBool revisit_right_cache; // [sync] flag for inner consistency checks 709 710 PetscFunctionBegin; 711 PetscValidHeaderSpecific(ts, TS_CLASSID, 1); 712 713 if (!ts->event) PetscFunctionReturn(PETSC_SUCCESS); 714 event = ts->event; 715 event->nevents_zero = 0; 716 revisit_right_cache = event->revisit_right; 717 for (PetscInt i = 0; i < event->nevents; i++) event->side[i] = 2; // side's are reset on each new iteration 718 if (event->iterctr == 0) 719 for (PetscInt i = 0; i < event->nevents; i++) event->justrefined_AB[i] = PETSC_FALSE; 720 721 PetscCall(TSGetTime(ts, &t)); 722 if (!event->processing) { // update the caches 723 PetscReal dt; 724 PetscCall(TSGetTimeStep(ts, &dt)); 725 event->ptime_cache = t; 726 event->timestep_cache = dt; // the next TS move is planned to be: t -> t+dt 727 } 728 729 PetscCall(TSGetSolution(ts, &U)); // if revisiting, this will be the updated U* (see discussion on "Revisiting" in the Developer notes above) 730 if (event->revisit_right) { 731 PetscReal tr = TSEvent_update_from_right(event); 732 PetscCheck(PetscAbsReal(tr - t) < PETSC_SMALL, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Inconsistent time value when performing 'revisiting' in TSEventHandler()"); 733 } else { 734 PetscCall(VecLockReadPush(U)); 735 PetscCallBack("TSEvent indicator", (*event->indicator)(ts, t, U, event->fvalue, event->ctx)); // fill fvalue's at point 't' 736 PetscCall(VecLockReadPop(U)); 737 TSEventCalcSigns(event->nevents, event->fvalue, event->vtol, event->fsign); // fill fvalue signs 738 } 739 PetscCall(TSEventTestZero(ts, t)); // check if the current point 't' is the event location; event->side[] may get updated 740 741 for (PetscInt i = 0; i < event->nevents; i++) { // check for brackets on the left/right of 't' 742 if (event->side[i] != 0) event->side[i] = TSEventTestBracket(event->fsign_prev[i], event->fsign[i], event->fsign_right[i], event->direction[i], event->iterctr); 743 minsidein = PetscMin(minsidein, event->side[i]); 744 } 745 PetscCall(MPIU_Allreduce(&minsidein, &minsideout, 1, MPIU_INT, MPI_MIN, PetscObjectComm((PetscObject)ts))); 746 /* 747 minsideout (sync on all ranks) indicates the minimum of the following states: 748 -1 : [ptime_prev, t] is a bracket for some indicator-function-i 749 +1 : [t, ptime_right] is a bracket for some indicator-function-i 750 0 : t is a zero-crossing for some indicator-function-i 751 2 : none of the above 752 */ 753 PetscCheck(!event->revisit_right || minsideout == 0, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "minsideout != 0 when performing 'revisiting' in TSEventHandler()"); 754 755 if (minsideout == -1 || minsideout == +1) { // this if-branch will refine the left/right bracket 756 const PetscReal bracket_size = (minsideout == -1) ? t - event->ptime_prev : event->ptime_right - t; // sync on all ranks 757 758 if (minsideout == +1 && bracket_size <= event->timestep_min) { // check if the bracket (right) is small 759 // [--------------------|-] 760 dt_next = bracket_size; // need one more step to get to event->ptime_right 761 event->revisit_right = PETSC_TRUE; 762 TSEvent_update_left(event, t); 763 if (event->monitor) 764 PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - reached too small bracket [%g - %g], next stepping to its right end %g (revisiting)\n", PetscGlobalRank, event->iterctr, (double)event->ptime_prev, 765 (double)event->ptime_right, (double)(event->ptime_prev + dt_next))); 766 } else { // the bracket is not very small -> refine it 767 // [--------|-------------] 768 if (bracket_size <= 2 * event->timestep_min) dt_next = bracket_size / 2; // the bracket is almost small -> bisect it 769 else { // the bracket is not small -> use Anderson-Bjorck 770 PetscReal dti_min = PETSC_MAX_REAL; 771 for (PetscInt i = 0; i < event->nevents; i++) { 772 if (event->side[i] == minsideout) { // only refine the appropriate brackets 773 PetscReal dti = RefineAndersonBjorck(event->ptime_prev, t, event->ptime_right, event->fvalue_prev[i], event->fvalue[i], event->fvalue_right[i], event->side[i], &event->side_prev[i], event->justrefined_AB[i], &event->gamma_AB[i]); 774 dti_min = PetscMin(dti_min, dti); 775 } 776 } 777 PetscCall(MPIU_Allreduce(&dti_min, &dt_next, 1, MPIU_REAL, MPIU_MIN, PetscObjectComm((PetscObject)ts))); 778 if (dt_next < event->timestep_min) dt_next = event->timestep_min; 779 if (bracket_size - dt_next < event->timestep_min) dt_next = bracket_size - event->timestep_min; 780 } 781 782 if (minsideout == -1) { // minsideout == -1, update the right-end values, retain the left-end values 783 TSEvent_update_right(event, t); 784 PetscCall(TSRollBack(ts)); 785 PetscCall(TSSetConvergedReason(ts, TS_CONVERGED_ITERATING)); // e.g. to override TS_CONVERGED_TIME on reaching ts->max_time 786 } else TSEvent_update_left(event, t); // minsideout == +1, update the left-end values, retain the right-end values 787 788 for (PetscInt i = 0; i < event->nevents; i++) { // update the "Anderson-Bjorck" flags 789 if (event->side[i] == minsideout) { 790 event->justrefined_AB[i] = PETSC_TRUE; // only for these i's Anderson-Bjorck was invoked 791 if (event->monitor) 792 PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - Event %" PetscInt_FMT " refining the bracket with sign change [%g - %g], next stepping to %g\n", PetscGlobalRank, event->iterctr, i, (double)event->ptime_prev, 793 (double)event->ptime_right, (double)(event->ptime_prev + dt_next))); 794 } else event->justrefined_AB[i] = PETSC_FALSE; // for these i's Anderson-Bjorck was not invoked 795 } 796 } 797 event->iterctr++; 798 event->processing = PETSC_TRUE; 799 } else if (minsideout == 0) { // found the appropriate zero-crossing (and no brackets to the left), finishing! 800 // [--------0-------------] 801 finished = PETSC_TRUE; 802 event->revisit_right = PETSC_FALSE; 803 for (PetscInt i = 0; i < event->nevents; i++) 804 if (event->side[i] == minsideout) { 805 event->events_zero[event->nevents_zero++] = i; 806 if (event->fsign[i] == 0) { // vtol was engaged 807 if (event->monitor) 808 PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - Event %" PetscInt_FMT " zero crossing located at time %g (tol=%g)\n", PetscGlobalRank, event->iterctr, i, (double)t, (double)event->vtol[i])); 809 } else { // dt_min was engaged 810 event->fsign[i] = 0; // sign = 0 is enforced further 811 if (event->monitor) 812 PetscCall(PetscViewerASCIIPrintf(event->monitor, "[%d] TSEvent: iter %" PetscInt_FMT " - Event %" PetscInt_FMT " accepting time %g as event location, due to reaching too small bracket [%g - %g]\n", PetscGlobalRank, event->iterctr, i, (double)t, 813 (double)event->ptime_prev, (double)t)); 814 } 815 } 816 event->iterctr++; 817 event->processing = PETSC_TRUE; 818 } else { // minsideout == 2: no brackets, no zero-crossings 819 // [----------------------] 820 PetscCheck(event->iterctr == 0, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state (event->iterctr != 0) in TSEventHandler()"); 821 if (event->processing) PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, event->timestep_cache, PETSC_FALSE))); 822 event->processing = PETSC_FALSE; 823 } 824 825 // if 'revisit_right' was flagged before the current iteration started, the iteration is expected to finish 826 PetscCheck(!revisit_right_cache || finished, PetscObjectComm((PetscObject)ts), PETSC_ERR_PLIB, "Unexpected state of 'revisit_right_cache' in TSEventHandler()"); 827 828 if (finished) { // finished handling the current event 829 PetscCall(TSPostEvent(ts, t, U)); 830 831 PetscReal dt; 832 PetscBool user_dt = PETSC_FALSE; 833 if (event->timestep_postevent > 0) { 834 dt = event->timestep_postevent; // user-provided post-event dt 835 event->processing = PETSC_FALSE; 836 user_dt = PETSC_TRUE; 837 } else { 838 dt = event->ptime_cache - t; // 'petsc-decide' the post-event dt 839 event->processing = PETSC_TRUE; 840 if (PetscAbsReal(dt) < PETSC_SMALL) { 841 dt = event->timestep_cache; // we hit the event, continue with the cached time step 842 event->processing = PETSC_FALSE; 843 } 844 } 845 PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt, user_dt))); 846 event->iterctr = 0; 847 } // if-finished 848 849 if (event->iterctr == 0) TSEvent_update_left(event, t); // not found an event, or finished the event 850 else { 851 PetscCall(TSGetTime(ts, &t)); // update 't' to account for potential rollback 852 PetscCall(TSSetTimeStep(ts, TSEvent_dt_cap(ts, t, dt_next, PETSC_FALSE))); // continue resolving the event 853 } 854 PetscFunctionReturn(PETSC_SUCCESS); 855 } 856 857 PetscErrorCode TSAdjointEventHandler(TS ts) 858 { 859 TSEvent event; 860 PetscReal t; 861 Vec U; 862 PetscInt ctr; 863 PetscBool forwardsolve = PETSC_FALSE; // Flag indicating that TS is doing an adjoint solve 864 865 PetscFunctionBegin; 866 PetscValidHeaderSpecific(ts, TS_CLASSID, 1); 867 if (!ts->event) PetscFunctionReturn(PETSC_SUCCESS); 868 event = ts->event; 869 870 PetscCall(TSGetTime(ts, &t)); 871 PetscCall(TSGetSolution(ts, &U)); 872 873 ctr = event->recorder.ctr - 1; 874 if (ctr >= 0 && PetscAbsReal(t - event->recorder.time[ctr]) < PETSC_SMALL) { 875 // Call the user post-event function 876 if (event->postevent) { 877 PetscCallBack("TSEvent post-event processing", (*event->postevent)(ts, event->recorder.nevents[ctr], event->recorder.eventidx[ctr], t, U, forwardsolve, event->ctx)); 878 event->recorder.ctr--; 879 } 880 } 881 PetscCall(PetscBarrier((PetscObject)ts)); 882 PetscFunctionReturn(PETSC_SUCCESS); 883 } 884 885 /*@ 886 TSGetNumEvents - Get the number of events defined on the given MPI process 887 888 Logically Collective 889 890 Input Parameter: 891 . ts - the `TS` context 892 893 Output Parameter: 894 . nevents - the number of local events on each MPI process 895 896 Level: intermediate 897 898 .seealso: [](ch_ts), `TSEvent`, `TSSetEventHandler()` 899 @*/ 900 PetscErrorCode TSGetNumEvents(TS ts, PetscInt *nevents) 901 { 902 PetscFunctionBegin; 903 *nevents = ts->event->nevents; 904 PetscFunctionReturn(PETSC_SUCCESS); 905 } 906