1 static char help[] = "Performs adjoint sensitivity analysis for a van der Pol like \n\ 2 equation with time dependent parameters using two approaches : \n\ 3 track : track only local sensitivities at each adjoint step \n\ 4 and accumulate them in a global array \n\ 5 global : track parameters at all timesteps together \n\ 6 Choose one of the two at runtime by -sa_method {track,global}. \n"; 7 8 /* 9 Concepts: TS^adjoint for time dependent parameters 10 Concepts: TS^Customized adjoint monitor based sensitivity tracking 11 Concepts: TS^All at once approach to sensitivity tracking 12 Processors: 1 13 */ 14 15 /* 16 Simple example to demonstrate TSAdjoint capabilities for time dependent params 17 without integral cost terms using either a tracking or global method. 18 19 Modify the Van Der Pol Eq to : 20 [u1'] = [mu1(t)*u1] 21 [u2'] = [mu2(t)*((1-u1^2)*u2-u1)] 22 (with initial conditions & params independent) 23 24 Define uref to be solution with initail conditions (2,-2/3), mu=(1,1e3) 25 - u_ref : (1.5967,-1.02969) 26 27 Define const function as cost = 2-norm(u - u_ref); 28 29 Initialization for the adjoint TS : 30 - dcost/dy|final_time = 2*(u-u_ref)|final_time 31 - dcost/dp|final_time = 0 32 33 The tracking method only tracks local sensitivity at each time step 34 and accumulates these sensitivities in a global array. Since the structure 35 of the equations being solved at each time step does not change, the jacobian 36 wrt parameters is defined analogous to constant RHSJacobian for a liner 37 TSSolve and the size of the jacP is independent of the number of time 38 steps. Enable this mode of adjoint analysis by -sa_method track. 39 40 The global method combines the parameters at all timesteps and tracks them 41 together. Thus, the columns of the jacP matrix are filled dependent upon the 42 time step. Also, the dimensions of the jacP matrix now depend upon the number 43 of time steps. Enable this mode of adjoint analysis by -sa_method global. 44 45 Since the equations here have parameters at predefined time steps, this 46 example should be run with non adaptive time stepping solvers only. This 47 can be ensured by -ts_adapt_type none (which is the default behavior only 48 for certain TS solvers like TSCN. If using an explicit TS like TSRK, 49 please be sure to add the aforementioned option to disable adaptive 50 timestepping.) 51 */ 52 53 /* 54 Include "petscts.h" so that we can use TS solvers. Note that this file 55 automatically includes: 56 petscsys.h - base PETSc routines petscvec.h - vectors 57 petscmat.h - matrices 58 petscis.h - index sets petscksp.h - Krylov subspace methods 59 petscviewer.h - viewers petscpc.h - preconditioners 60 petscksp.h - linear solvers petscsnes.h - nonlinear solvers 61 */ 62 #include <petscts.h> 63 64 extern PetscErrorCode RHSFunction(TS ,PetscReal ,Vec ,Vec ,void *); 65 extern PetscErrorCode RHSJacobian(TS ,PetscReal ,Vec ,Mat ,Mat ,void *); 66 extern PetscErrorCode RHSJacobianP_track(TS ,PetscReal ,Vec ,Mat ,void *); 67 extern PetscErrorCode RHSJacobianP_global(TS ,PetscReal ,Vec ,Mat ,void *); 68 extern PetscErrorCode Monitor(TS ,PetscInt ,PetscReal ,Vec ,void *); 69 extern PetscErrorCode AdjointMonitor(TS ,PetscInt ,PetscReal ,Vec ,PetscInt ,Vec *, Vec *,void *); 70 71 /* 72 User-defined application context - contains data needed by the 73 application-provided call-back routines. 74 */ 75 76 typedef struct { 77 /*------------- Forward solve data structures --------------*/ 78 PetscInt max_steps; /* number of steps to run ts for */ 79 PetscReal final_time; /* final time to integrate to*/ 80 PetscReal time_step; /* ts integration time step */ 81 Vec mu1; /* time dependent params */ 82 Vec mu2; /* time dependent params */ 83 Vec U; /* solution vector */ 84 Mat A; /* Jacobian matrix */ 85 86 /*------------- Adjoint solve data structures --------------*/ 87 Mat Jacp; /* JacobianP matrix */ 88 Vec lambda; /* adjoint variable */ 89 Vec mup; /* adjoint variable */ 90 91 /*------------- Global accumation vecs for monitor based tracking --------------*/ 92 Vec sens_mu1; /* global sensitivity accumulation */ 93 Vec sens_mu2; /* global sensitivity accumulation */ 94 PetscInt adj_idx; /* to keep track of adjoint solve index */ 95 } AppCtx; 96 97 typedef enum {SA_TRACK, SA_GLOBAL} SAMethod; 98 static const char *const SAMethods[] = {"TRACK","GLOBAL","SAMethod","SA_",0}; 99 100 /* ----------------------- Explicit form of the ODE -------------------- */ 101 102 PetscErrorCode RHSFunction(TS ts,PetscReal t,Vec U,Vec F,void *ctx) 103 { 104 AppCtx *user = (AppCtx*) ctx; 105 PetscScalar *f; 106 PetscInt curr_step; 107 const PetscScalar *u; 108 const PetscScalar *mu1; 109 const PetscScalar *mu2; 110 111 PetscFunctionBeginUser; 112 PetscCall(TSGetStepNumber(ts,&curr_step)); 113 PetscCall(VecGetArrayRead(U,&u)); 114 PetscCall(VecGetArrayRead(user->mu1,&mu1)); 115 PetscCall(VecGetArrayRead(user->mu2,&mu2)); 116 PetscCall(VecGetArray(F,&f)); 117 f[0] = mu1[curr_step]*u[1]; 118 f[1] = mu2[curr_step]*((1.-u[0]*u[0])*u[1]-u[0]); 119 PetscCall(VecRestoreArrayRead(U,&u)); 120 PetscCall(VecRestoreArrayRead(user->mu1,&mu1)); 121 PetscCall(VecRestoreArrayRead(user->mu2,&mu2)); 122 PetscCall(VecRestoreArray(F,&f)); 123 PetscFunctionReturn(0); 124 } 125 126 PetscErrorCode RHSJacobian(TS ts,PetscReal t,Vec U,Mat A,Mat B,void *ctx) 127 { 128 AppCtx *user = (AppCtx*) ctx; 129 PetscInt rowcol[] = {0,1}; 130 PetscScalar J[2][2]; 131 PetscInt curr_step; 132 const PetscScalar *u; 133 const PetscScalar *mu1; 134 const PetscScalar *mu2; 135 136 PetscFunctionBeginUser; 137 PetscCall(TSGetStepNumber(ts,&curr_step)); 138 PetscCall(VecGetArrayRead(user->mu1,&mu1)); 139 PetscCall(VecGetArrayRead(user->mu2,&mu2)); 140 PetscCall(VecGetArrayRead(U,&u)); 141 J[0][0] = 0; 142 J[1][0] = -mu2[curr_step]*(2.0*u[1]*u[0]+1.); 143 J[0][1] = mu1[curr_step]; 144 J[1][1] = mu2[curr_step]*(1.0-u[0]*u[0]); 145 PetscCall(MatSetValues(A,2,rowcol,2,rowcol,&J[0][0],INSERT_VALUES)); 146 PetscCall(MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY)); 147 PetscCall(MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY)); 148 PetscCall(VecRestoreArrayRead(U,&u)); 149 PetscCall(VecRestoreArrayRead(user->mu1,&mu1)); 150 PetscCall(VecRestoreArrayRead(user->mu2,&mu2)); 151 PetscFunctionReturn(0); 152 } 153 154 /* ------------------ Jacobian wrt parameters for tracking method ------------------ */ 155 156 PetscErrorCode RHSJacobianP_track(TS ts,PetscReal t,Vec U,Mat A,void *ctx) 157 { 158 PetscInt row[] = {0,1},col[] = {0,1}; 159 PetscScalar J[2][2]; 160 const PetscScalar *u; 161 162 PetscFunctionBeginUser; 163 PetscCall(VecGetArrayRead(U,&u)); 164 J[0][0] = u[1]; 165 J[1][0] = 0; 166 J[0][1] = 0; 167 J[1][1] = (1.-u[0]*u[0])*u[1]-u[0]; 168 PetscCall(MatSetValues(A,2,row,2,col,&J[0][0],INSERT_VALUES)); 169 PetscCall(MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY)); 170 PetscCall(MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY)); 171 PetscCall(VecRestoreArrayRead(U,&u)); 172 PetscFunctionReturn(0); 173 } 174 175 /* ------------------ Jacobian wrt parameters for global method ------------------ */ 176 177 PetscErrorCode RHSJacobianP_global(TS ts,PetscReal t,Vec U,Mat A,void *ctx) 178 { 179 PetscInt row[] = {0,1},col[] = {0,1}; 180 PetscScalar J[2][2]; 181 const PetscScalar *u; 182 PetscInt curr_step; 183 184 PetscFunctionBeginUser; 185 PetscCall(TSGetStepNumber(ts,&curr_step)); 186 PetscCall(VecGetArrayRead(U,&u)); 187 J[0][0] = u[1]; 188 J[1][0] = 0; 189 J[0][1] = 0; 190 J[1][1] = (1.-u[0]*u[0])*u[1]-u[0]; 191 col[0] = (curr_step)*2; 192 col[1] = (curr_step)*2+1; 193 PetscCall(MatSetValues(A,2,row,2,col,&J[0][0],INSERT_VALUES)); 194 PetscCall(MatAssemblyBegin(A,MAT_FINAL_ASSEMBLY)); 195 PetscCall(MatAssemblyEnd(A,MAT_FINAL_ASSEMBLY)); 196 PetscCall(VecRestoreArrayRead(U,&u)); 197 PetscFunctionReturn(0); 198 } 199 200 /* Dump solution to console if called */ 201 PetscErrorCode Monitor(TS ts,PetscInt step,PetscReal t,Vec U,void *ctx) 202 { 203 PetscFunctionBeginUser; 204 PetscCall(PetscPrintf(PETSC_COMM_WORLD,"\n Solution at time %e is \n", t)); 205 PetscCall(VecView(U,PETSC_VIEWER_STDOUT_WORLD)); 206 PetscFunctionReturn(0); 207 } 208 209 /* Customized adjoint monitor to keep track of local 210 sensitivities by storing them in a global sensitivity array. 211 Note : This routine is only used for the tracking method. */ 212 PetscErrorCode AdjointMonitor(TS ts,PetscInt steps,PetscReal time,Vec u,PetscInt numcost,Vec *lambda, Vec *mu,void *ctx) 213 { 214 AppCtx *user = (AppCtx*) ctx; 215 PetscInt curr_step; 216 PetscScalar *sensmu1_glob; 217 PetscScalar *sensmu2_glob; 218 const PetscScalar *sensmu_loc; 219 220 PetscFunctionBeginUser; 221 PetscCall(TSGetStepNumber(ts,&curr_step)); 222 /* Note that we skip the first call to the monitor in the adjoint 223 solve since the sensitivities are already set (during 224 initialization of adjoint vectors). 225 We also note that each indvidial TSAdjointSolve calls the monitor 226 twice, once at the step it is integrating from and once at the step 227 it integrates to. Only the second call is useful for transferring 228 local sensitivities to the global array. */ 229 if (curr_step == user->adj_idx) { 230 PetscFunctionReturn(0); 231 } else { 232 PetscCall(VecGetArrayRead(*mu,&sensmu_loc)); 233 PetscCall(VecGetArray(user->sens_mu1,&sensmu1_glob)); 234 PetscCall(VecGetArray(user->sens_mu2,&sensmu2_glob)); 235 sensmu1_glob[curr_step] = sensmu_loc[0]; 236 sensmu2_glob[curr_step] = sensmu_loc[1]; 237 PetscCall(VecRestoreArray(user->sens_mu1,&sensmu1_glob)); 238 PetscCall(VecRestoreArray(user->sens_mu2,&sensmu2_glob)); 239 PetscCall(VecRestoreArrayRead(*mu,&sensmu_loc)); 240 PetscFunctionReturn(0); 241 } 242 } 243 244 int main(int argc,char **argv) 245 { 246 TS ts; 247 AppCtx user; 248 PetscScalar *x_ptr,*y_ptr,*u_ptr; 249 PetscMPIInt size; 250 PetscBool monitor = PETSC_FALSE; 251 SAMethod sa = SA_GLOBAL; 252 PetscErrorCode ierr; 253 254 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 255 Initialize program 256 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 257 PetscCall(PetscInitialize(&argc,&argv,NULL,help)); 258 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD,&size)); 259 PetscCheck(size == 1,PETSC_COMM_WORLD,PETSC_ERR_WRONG_MPI_SIZE,"This is a uniprocessor example only!"); 260 261 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 262 Set runtime options 263 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 264 ierr = PetscOptionsBegin(PETSC_COMM_WORLD,NULL,"SA analysis options.","");PetscCall(ierr);{ 265 PetscCall(PetscOptionsGetBool(NULL,NULL,"-monitor",&monitor,NULL)); 266 PetscCall(PetscOptionsEnum("-sa_method","Sensitivity analysis method (track or global)","",SAMethods,(PetscEnum)sa,(PetscEnum*)&sa,NULL)); 267 } 268 ierr = PetscOptionsEnd();PetscCall(ierr); 269 270 user.final_time = 0.1; 271 user.max_steps = 5; 272 user.time_step = user.final_time/user.max_steps; 273 274 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 275 Create necessary matrix and vectors for forward solve. 276 Create Jacp matrix for adjoint solve. 277 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 278 PetscCall(VecCreateSeq(PETSC_COMM_WORLD,user.max_steps,&user.mu1)); 279 PetscCall(VecCreateSeq(PETSC_COMM_WORLD,user.max_steps,&user.mu2)); 280 PetscCall(VecSet(user.mu1,1.25)); 281 PetscCall(VecSet(user.mu2,1.0e2)); 282 283 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 284 For tracking method : create the global sensitivity array to 285 accumulate sensitivity with respect to parameters at each step. 286 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 287 if (sa == SA_TRACK) { 288 PetscCall(VecCreateSeq(PETSC_COMM_WORLD,user.max_steps,&user.sens_mu1)); 289 PetscCall(VecCreateSeq(PETSC_COMM_WORLD,user.max_steps,&user.sens_mu2)); 290 } 291 292 PetscCall(MatCreate(PETSC_COMM_WORLD,&user.A)); 293 PetscCall(MatSetSizes(user.A,PETSC_DECIDE,PETSC_DECIDE,2,2)); 294 PetscCall(MatSetFromOptions(user.A)); 295 PetscCall(MatSetUp(user.A)); 296 PetscCall(MatCreateVecs(user.A,&user.U,NULL)); 297 298 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 299 Note that the dimensions of the Jacp matrix depend upon the 300 sensitivity analysis method being used ! 301 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 302 PetscCall(MatCreate(PETSC_COMM_WORLD,&user.Jacp)); 303 if (sa == SA_TRACK) { 304 PetscCall(MatSetSizes(user.Jacp,PETSC_DECIDE,PETSC_DECIDE,2,2)); 305 } 306 if (sa == SA_GLOBAL) { 307 PetscCall(MatSetSizes(user.Jacp,PETSC_DECIDE,PETSC_DECIDE,2,user.max_steps*2)); 308 } 309 PetscCall(MatSetFromOptions(user.Jacp)); 310 PetscCall(MatSetUp(user.Jacp)); 311 312 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 313 Create timestepping solver context 314 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 315 PetscCall(TSCreate(PETSC_COMM_WORLD,&ts)); 316 PetscCall(TSSetEquationType(ts,TS_EQ_ODE_EXPLICIT)); 317 PetscCall(TSSetType(ts,TSCN)); 318 319 PetscCall(TSSetRHSFunction(ts,NULL,RHSFunction,&user)); 320 PetscCall(TSSetRHSJacobian(ts,user.A,user.A,RHSJacobian,&user)); 321 if (sa == SA_TRACK) { 322 PetscCall(TSSetRHSJacobianP(ts,user.Jacp,RHSJacobianP_track,&user)); 323 } 324 if (sa == SA_GLOBAL) { 325 PetscCall(TSSetRHSJacobianP(ts,user.Jacp,RHSJacobianP_global,&user)); 326 } 327 328 PetscCall(TSSetExactFinalTime(ts,TS_EXACTFINALTIME_MATCHSTEP)); 329 PetscCall(TSSetMaxTime(ts,user.final_time)); 330 PetscCall(TSSetTimeStep(ts,user.final_time/user.max_steps)); 331 332 if (monitor) { 333 PetscCall(TSMonitorSet(ts,Monitor,&user,NULL)); 334 } 335 if (sa == SA_TRACK) { 336 PetscCall(TSAdjointMonitorSet(ts,AdjointMonitor,&user,NULL)); 337 } 338 339 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 340 Set initial conditions 341 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 342 PetscCall(VecGetArray(user.U,&x_ptr)); 343 x_ptr[0] = 2.0; 344 x_ptr[1] = -2.0/3.0; 345 PetscCall(VecRestoreArray(user.U,&x_ptr)); 346 347 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 348 Save trajectory of solution so that TSAdjointSolve() may be used 349 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 350 PetscCall(TSSetSaveTrajectory(ts)); 351 352 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 353 Set runtime options 354 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 355 PetscCall(TSSetFromOptions(ts)); 356 357 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 358 Execute forward model and print solution. 359 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 360 PetscCall(TSSolve(ts,user.U)); 361 PetscCall(PetscPrintf(PETSC_COMM_WORLD,"\n Solution of forward TS :\n")); 362 PetscCall(VecView(user.U,PETSC_VIEWER_STDOUT_WORLD)); 363 PetscCall(PetscPrintf(PETSC_COMM_WORLD,"\n Forward TS solve successfull! Adjoint run begins!\n")); 364 365 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 366 Adjoint model starts here! Create adjoint vectors. 367 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 368 PetscCall(MatCreateVecs(user.A,&user.lambda,NULL)); 369 PetscCall(MatCreateVecs(user.Jacp,&user.mup,NULL)); 370 371 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 372 Set initial conditions for the adjoint vector 373 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 374 PetscCall(VecGetArray(user.U,&u_ptr)); 375 PetscCall(VecGetArray(user.lambda,&y_ptr)); 376 y_ptr[0] = 2*(u_ptr[0] - 1.5967); 377 y_ptr[1] = 2*(u_ptr[1] - -(1.02969)); 378 PetscCall(VecRestoreArray(user.lambda,&y_ptr)); 379 PetscCall(VecRestoreArray(user.U,&y_ptr)); 380 PetscCall(VecSet(user.mup,0)); 381 382 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 383 Set number of cost functions. 384 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 385 PetscCall(TSSetCostGradients(ts,1,&user.lambda,&user.mup)); 386 387 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 388 The adjoint vector mup has to be reset for each adjoint step when 389 using the tracking method as we want to treat the parameters at each 390 time step one at a time and prevent accumulation of the sensitivities 391 from parameters at previous time steps. 392 This is not necessary for the global method as each time dependent 393 parameter is treated as an independent parameter. 394 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 395 if (sa == SA_TRACK) { 396 for (user.adj_idx=user.max_steps; user.adj_idx>0; user.adj_idx--) { 397 PetscCall(VecSet(user.mup,0)); 398 PetscCall(TSAdjointSetSteps(ts, 1)); 399 PetscCall(TSAdjointSolve(ts)); 400 } 401 } 402 if (sa == SA_GLOBAL) { 403 PetscCall(TSAdjointSolve(ts)); 404 } 405 406 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 407 Dispaly adjoint sensitivities wrt parameters and initial conditions 408 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 409 if (sa == SA_TRACK) { 410 PetscCall(PetscPrintf(PETSC_COMM_WORLD,"\n sensitivity wrt mu1: d[cost]/d[mu1]\n")); 411 PetscCall(VecView(user.sens_mu1,PETSC_VIEWER_STDOUT_WORLD)); 412 PetscCall(PetscPrintf(PETSC_COMM_WORLD,"\n sensitivity wrt mu2: d[cost]/d[mu2]\n")); 413 PetscCall(VecView(user.sens_mu2,PETSC_VIEWER_STDOUT_WORLD)); 414 } 415 416 if (sa == SA_GLOBAL) { 417 ierr = PetscPrintf(PETSC_COMM_WORLD,"\n sensitivity wrt params: d[cost]/d[p], where p refers to \n\ 418 the interlaced vector made by combining mu1,mu2\n");PetscCall(ierr); 419 PetscCall(VecView(user.mup,PETSC_VIEWER_STDOUT_WORLD)); 420 } 421 422 PetscCall(PetscPrintf(PETSC_COMM_WORLD,"\n sensitivity wrt initial conditions: d[cost]/d[u(t=0)]\n")); 423 PetscCall(VecView(user.lambda,PETSC_VIEWER_STDOUT_WORLD)); 424 425 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 426 Free work space! 427 All PETSc objects should be destroyed when they are no longer needed. 428 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 429 PetscCall(MatDestroy(&user.A)); 430 PetscCall(MatDestroy(&user.Jacp)); 431 PetscCall(VecDestroy(&user.U)); 432 PetscCall(VecDestroy(&user.lambda)); 433 PetscCall(VecDestroy(&user.mup)); 434 PetscCall(VecDestroy(&user.mu1)); 435 PetscCall(VecDestroy(&user.mu2)); 436 if (sa == SA_TRACK) { 437 PetscCall(VecDestroy(&user.sens_mu1)); 438 PetscCall(VecDestroy(&user.sens_mu2)); 439 } 440 PetscCall(TSDestroy(&ts)); 441 PetscCall(PetscFinalize()); 442 return(ierr); 443 } 444 445 /*TEST 446 447 test: 448 requires: !complex 449 suffix : track 450 args : -sa_method track 451 452 test: 453 requires: !complex 454 suffix : global 455 args : -sa_method global 456 457 TEST*/ 458