gel.scripts.inverse
The inverse model.
RECOMMENDED USAGE
nohup inverse ... | tee -a out.txt &
This will allow it to continue in the background even if the terminal session dies but the user session remains. Also will log error messages that may be hard-coded to stdout by FEniCS.
Side-Effects
Will interact with local file system when run as a command. This includes:
- Logs what it is doing in file specified by name
LOG_FILE - Uses a directory
results_diras a super-directory for all inverse model results. This directory should be clear of subdirectories except for those created by specifically this program. - Creates a .csv file
inverse.csvunderresults_dirto store a chart of all options used and some result info like runtime and quantites for L-curve analysis. - Creates a subdirectory under
results_diraccording to the options provided. - In that subdirectory, stores many .xdmf files with results and other information.
- If a subdirectory for a collection of settings already exists, will not proceed forward and quietly skip.
API
1"""The inverse model. 2 3# RECOMMENDED USAGE 4 nohup inverse ... | tee -a out.txt & 5 6This will allow it to continue in the background even if the terminal 7session dies but the user session remains. Also will log error messages 8that may be hard-coded to stdout by FEniCS. 9 10# Side-Effects 11 12Will interact with local file system when run as a command. This 13includes: 14* Logs what it is doing in file specified by name `LOG_FILE` 15* Uses a directory `results_dir` as a super-directory for all inverse 16model results. This directory should be clear of subdirectories except 17for those created by **specifically this program**. 18* Creates a .csv file `inverse.csv` under `results_dir` to store a chart 19of all options used and some result info like runtime and quantites 20for L-curve analysis. 21* Creates a subdirectory under `results_dir` according to the options 22provided. 23* In that subdirectory, stores many .xdmf files with results and 24other information. 25* If a subdirectory for a collection of settings already exists, will 26not proceed forward and quietly skip. 27 28# API 29""" 30from gel import * 31import numpy as np 32import os 33from sanity_utils import * 34import dolfin as df 35import moola 36 37 38INTERM_SAVE_PERIOD = 10 39"""Write current guess to filesytem every (this many) L-BFGS iterations""" 40 41 42############################################################################# 43### FEniCS settings 44############################################################################# 45parameters['linear_algebra_backend'] = 'PETSc' 46parameters['form_compiler']['representation'] = 'uflacs' 47parameters['form_compiler']['optimize'] = True 48parameters['form_compiler']['cpp_optimize'] = True 49parameters['form_compiler']['quadrature_degree'] = 3 50 51 52LOG_FILE = "global_log.txt" 53 54 55def _expand_obj_info(exp_info): 56 obj_info = exp_info[5] 57 new_exp_info = ( 58 *exp_info[:5], 59 obj_info.gamma_num, 60 obj_info.objective_type, 61 obj_info.regularization_type, 62 obj_info.objective_domain, 63 obj_info.regularization_domain, 64 obj_info.detectable_u_cutoff, 65 obj_info.safe_u_weight_filename(), 66 obj_info.apply_u_weight_to_reg, 67 *exp_info[6:] 68 ) 69 return new_exp_info 70 71 72INVERSE_EXPERIMENT_COLS = [ 73 "Results Dir", 74 "Cell Name", 75 "target_file", 76 "Formulation", 77 "k", 78 "gamma", 79 "objective_type", 80 "regularization_type", 81 "objective_domain", 82 "regularization_domain", 83 "detectable_u_cutoff", 84 "u_weight_filename", 85 "apply_u_weight_to_reg", 86 "lower_bound", 87 "upper_bound", 88 "tol", 89 "mod_repr_init", 90 "debug_mode", 91 "max_iter", 92 "load_steps", 93 "restrict_ctl_dofs_to_veh", 94 "preconditioner", 95 "optimizer_backend", 96 "bci", 97 "bco", 98 "mu_ff", 99 "u_init", 100 "outcome", 101 "pure_obj", 102 "reg", 103 "time" 104] 105"""Names of columns in `inverse.csv`""" 106 107 108def main(args): 109 """Sets up logging to relevant files and starts the inverse model. 110 111 * `args`: `argparse.Namespace` with parsed command arguments 112 113 Side-effects: see intro to `gel.scripts.inverse`; does all the file 114 handling except the experiment's subdirectory 115 116 Computation: calls `gel_inverse`, which handles the experiment's 117 subdirectory. 118 """ 119 logger = get_global_logger(LOG_FILE) 120 121 # Results 122 result_csv_path = os.path.join(args.r, "inverse.csv") 123 csv = ResultsCSV(result_csv_path, INVERSE_EXPERIMENT_COLS) 124 125 logger.info(f"Detected number of processes: {MPI.comm_world.Get_size()}") 126 127 objective_info = ObjectiveInfo(args) 128 129 exp_info = ( 130 args.r, 131 args.c, 132 args.t, 133 args.f, 134 args.k, 135 objective_info, 136 *args.b, 137 args.a, 138 args.i, 139 args.debug, 140 args.m, 141 args.l, 142 not args.no_restrict_ctl_dofs_to_veh, 143 args.p, 144 args.opt_backend, 145 args.bci, 146 args.bco, 147 args.mu, 148 args.u_init 149 ) 150 151 logger.info(f"Beginning experiment with arguments {exp_info}.") 152 153 # Be careful of it already existing 154 try: 155 try: 156 total_time, pure_obj, reg = gel_inverse(*exp_info) 157 158 # Update DataFrame 159 exp_info = _expand_obj_info(exp_info) 160 csv.add_row(list(exp_info) + [ 161 "Converged", 162 pure_obj, 163 reg, 164 float(total_time) 165 ]) 166 except RuntimeError as e: 167 logger.info( 168 f"FEniCS encountered an error. Recording this outcome" 169 ) 170 logger.info(e) 171 172 # Update DataFrame 173 exp_info = _expand_obj_info(exp_info) 174 csv.add_row(list(exp_info) + ["Crash", "", "", ""]) 175 176 # Save into file 177 csv.save() 178 except FileExistsError: 179 logger.info( 180 "Found that experiment directory already exists. Skipping..." 181 ) 182 except UnsupportedGeometryError: 183 logger.info( 184 "This geometry type is not supported by the inverse model at " 185 "this time. Skipping..." 186 ) 187 188 189class UnsupportedGeometryError(Exception): 190 """Raised if `cell_data_dir` doesn't have a supported type.""" 191 pass 192 193 194def gel_inverse( 195 results_dir, 196 cell_data_dir, 197 target_file, 198 formulation, 199 k, 200 objective_info, 201 lower_bound, 202 upper_bound, 203 tol, 204 mod_repr_init, 205 debug=False, 206 maxiter=10000, 207 load_steps=2, 208 restrict_ctl_dofs_to_veh=False, 209 preconditioner="hypre_amg", 210 optimizer_backend="scipy", 211 bci=None, 212 bco=None, 213 mu=108.0, 214 u_init=None 215 ): 216 r"""Runs the inverse model, writes subdirectory and logs progress. 217 218 * `results_dir`: str path to directory to put all results 219 * `cell_data_dir`: str path to directory with geometry information 220 to use, see the constructor to `gel.geometry.Geometry` for required 221 files 222 * `target_file`: str path to full-shape .xdmf file with 223 displacements in "u" 224 * `formulation`: str a valid material model formulation, see 225 `gel.mechanics` for options 226 * `k`: float $\frac{D_1}{c_1}$ ratio 227 * `objective_info`: `gel.objective.ObjectiveInfo` with information 228 on the objective to be minimized 229 * `lower_bound`: float; either the lower bound for the "beta_tilde" 230 formulation described in `gel.mechanics` or the lower bound for 231 L-BFGS-B if the "scipy" backend if selected 232 * `upper_bound`: float; either the upper bound for the "beta_tilde" 233 formulation described in `gel.mechanics` or the upper bound for 234 L-BFGS-B if the "scipy" backend if selected 235 * `tol`: float tolerance for inverse model convergence 236 * `mod_repr_init`: str a valid modulus representation for the 237 initial guess, see `gel.mechanics` for options 238 * `debug`: bool, if True runs a Taylor test and outputs a tree of 239 the traced components of the simulation in file "graph.dot" in the 240 experiment's subdirectory 241 * `maxiter`: int maximum number of L-BFGS iterations 242 * `restrict_ctl_dofs_to_veh`: bool, it True will only control DoFs 243 in the volume within the event horizon with cutoff specified in 244 `objective_info` 245 * `preconditioner`: str preconditioner to use, see 246 `gel.gel.ForwardSimulation` for more details 247 * `optimizer_backend`: str moola for L-BFGS and that library, 248 otherwise scipy for L-BFGS-B and that library 249 * `bci`: str path to .vtk file with inner BC info, see 250 `gel.geometry.Geometry` for details 251 * `bco`: str path to .vtk file with outer BC info, see 252 `gel.geometry.Geometry` for details 253 * `mu`: float far-field shear modulus from rheometry 254 * `u_init`: str path to full-shape .xdmf file with initial 255 displacements 256 257 Side-effects: writes many files in new subdirectory to 258 `results_dir`, see intro to `gel.scripts.inverse` 259 260 Returns: 261 * float time it took to run 262 * float objective functional value $O$ at end (see `gel.objective`) 263 * float regularization functional value $R$ at end (see 264 `gel.objective`) 265 """ 266 # Input validation 267 validate_formulation(formulation) 268 validate_mod_repr_field(mod_repr_init) 269 270 # Get cell name 271 cell_name = read_cell_name(cell_data_dir) 272 # Get geometry type 273 geo_type = read_geo_type(cell_data_dir) 274 if geo_type != "real_cell_gel": 275 raise UnsupportedGeometryError(f"{geo_type} currently not supported") 276 277 # Output directory handling 278 output_dir = prepare_experiment_dir( 279 results_dir, 280 cell_name, 281 formulation, 282 restrict_ctl_dofs_to_veh, 283 mod_repr_init, 284 k, 285 objective_info.gamma_num, 286 objective_info.objective_type, 287 objective_info.regularization_type, 288 objective_info.objective_domain, 289 objective_info.regularization_domain, 290 objective_info.detectable_u_cutoff, 291 objective_info.apply_u_weight_to_reg, 292 objective_info.safe_u_weight_filename(), 293 lower_bound, 294 upper_bound, 295 tol, 296 preconditioner, 297 bci, 298 bco, 299 mu 300 ) 301 302 # Setup logging 303 logger, logger_destructor = create_experiment_logger(output_dir) 304 305 # 306 # Initial forward run 307 # 308 logger.info(f"Beginning inverse model in {formulation} formulation.") 309 logger.info(f"Using cell data from {cell_data_dir}, '{cell_name}'") 310 logger.info(f"Using mod_repr init strat {mod_repr_init}") 311 logger.info( 312 f"Restricting mod_ctl to VEH? {restrict_ctl_dofs_to_veh}" 313 ) 314 logger.info(f"Uses geometry type {geo_type}") 315 logger.info(f"Target file: {target_file}") 316 logger.info(f"Using D1/C1: {k}") 317 logger.info(f"Bounds: [{lower_bound}, {upper_bound}]") 318 logger.info(f"Using tolerance: {tol}") 319 logger.info(f"Using maximum iterations: {maxiter}") 320 logger.info(f"Using load steps: {load_steps}") 321 logger.info(f"Optimizer backend: {optimizer_backend}") 322 logger.info(f"Preconditioner: {preconditioner}") 323 logger.info(f"Override inner BC: {bci}") 324 logger.info(f"Override outer BC: {bco}") 325 logger.info(f"Far field modulus: {mu}") 326 logger.info(f"Initial displacements: {u_init}") 327 objective_info.log_info(logger) 328 329 # RESET TAPE 330 set_working_tape(Tape()) 331 332 # Timer start 333 timer = ExperimentTimer() 334 timer.start() 335 336 logger.info(f"Running forward model with initial guess...") 337 338 # Deal with telling geo about detectable regions, BCs 339 addn_args = {"bci_data" : bci, "bco_data" : bco} 340 if objective_info.detectable_u_cutoff is not None: 341 addn_args["u_magnitude_subdomains_file"] = target_file 342 addn_args["detectable_u_cutoff"] = objective_info.detectable_u_cutoff 343 344 formulation_kwargs = dict() 345 if formulation == "beta_tilde": 346 formulation_kwargs["beta_min"] = lower_bound 347 formulation_kwargs["beta_max"] = upper_bound 348 349 sim = ForwardSimulation( 350 geo_type, 351 k, 352 formulation=formulation, 353 mod_repr_init=mod_repr_init, 354 load_steps=load_steps, 355 vprint=logger.info, 356 mu_ff=mu, 357 data_directory=cell_data_dir, 358 restrict_ctl_dofs_to_veh=restrict_ctl_dofs_to_veh, 359 pc=preconditioner, 360 u_init=u_init, 361 formulation_kwargs=formulation_kwargs, 362 **addn_args 363 ) 364 365 sim.run_forward() 366 kinematics, mod_repr = sim.kinematics, sim.mod_repr # Pointers 367 368 ##################### 369 # Must deal with intercepted block 370 tape = get_working_tape() 371 solve_block = tape.get_blocks()[-1] 372 373 for block_var in solve_block.get_dependencies(): 374 if block_var.output == kinematics.u: 375 block_var.save_output() 376 solve_block.block_var_to_save = block_var 377 solve_block.prev_soln = Function(sim.geo.V) 378 ##################### 379 380 # Save 381 geo = sim.geo 382 logger.info( 383 f"Finished initial guess forward run. Saving results to {output_dir}" 384 ) 385 kinematics_sim = Kinematics(geo, kinematics.u) 386 output_results_paraview( 387 output_dir, 388 kinematics_sim, 389 mod_repr, 390 name="init_guess" 391 ) 392 393 # Read in target displacement 394 logger.info(f"Reading target displacements from {target_file}") 395 kinematics_target = kinematics_from_file(geo, target_file) 396 397 # Debug 398 if debug and rank == 0: 399 tape = get_working_tape() 400 tape.visualise(os.path.join(output_dir, "graph.dot")) 401 402 # 403 # Objective function construction 404 # 405 logger.info(f"Assembling inverse objective.") 406 407 pre_assembly, pure_obj_form, reg_form = objective_info.get_objective_forms( 408 geo, 409 kinematics_target, 410 kinematics_sim, 411 mod_repr, 412 logger 413 ) 414 415 # Finalize 416 obj = assemble(pre_assembly) 417 418 # 419 # Inverse model 420 # 421 # Create callback 422 i = 0 423 def callback(this_mod_repr): 424 nonlocal i, geo, kinematics, output_dir, mod_repr, solve_block 425 nonlocal optimizer_backend 426 427 if i % INTERM_SAVE_PERIOD == 0: 428 logger.info(f"Saving results for iteration {i}") 429 430 df.Function.assign( 431 kinematics.u, 432 solve_block.block_var_to_save.output 433 ) 434 kinematics_mid = Kinematics(geo, kinematics.u) 435 436 # Assign values to mod_repr 437 if optimizer_backend == "scipy": 438 dm = mod_repr.function_space().dofmap() 439 local_range = dm.ownership_range() 440 mod_repr.vector().set_local( 441 this_mod_repr[local_range[0]:local_range[1]] 442 ) 443 mod_repr.vector().apply("") 444 elif optimizer_backend == "moola": 445 mod_repr.assign(this_mod_repr.data) 446 447 # Save 448 output_results_paraview( 449 output_dir, 450 kinematics_mid, 451 mod_repr, 452 name="most_recent" 453 ) 454 455 i += 1 456 457 # Adjoint stuff, marking tape 458 control = Control(sim.ctl) 459 obj_hat = ReducedFunctional(obj, control) 460 461 # Check the derivative 462 if debug: 463 debug_deriv( 464 obj_hat, 465 geo, 466 mod_repr, 467 logger 468 ) 469 470 # Minimize 471 logger.info(f"Calling minimization routine...") 472 #if MPI.comm_world.rank == 0: 473 #breakpoint() 474 if optimizer_backend == "scipy": 475 mod_repr_opt = minimize( 476 obj_hat, 477 method = "L-BFGS-B", 478 tol=tol, 479 bounds = (lower_bound, upper_bound), 480 options = {"maxiter":maxiter,"gtol":tol,"disp": True}, 481 callback = callback 482 ) 483 elif optimizer_backend == "moola": 484 problem = MoolaOptimizationProblem(obj_hat) 485 mod_repr_moola = moola.DolfinPrimalVector(sim.ctl) 486 solver = moola.BFGS( 487 problem, 488 mod_repr_moola, 489 options={ 490 "jtol":tol, 491 "gtol":100*tol, 492 "Hinit":"default", 493 "maxiter":maxiter, 494 "mem_lim":10, 495 "display":2 496 }, 497 hooks={"after_iteration":callback} 498 ) 499 sol = solver.solve() 500 mod_repr_opt = sol["control"].data 501 else: 502 raise ValueError(f"Unknown optimizer backend {optimizer_backend}") 503 504 logger.info(f"Finished minimization") 505 506 # Assignments after optimal parameters are found 507 mod_repr.assign(mod_repr_opt) 508 509 # 510 # Post-pro: run forward of optimal params and save 511 # 512 logger.info(f"Running forward model with optimal mod_repr field...") 513 514 # Run forward model with modified mod_repr 515 sim.run_forward() 516 logger.info(f"Finished. Saving...") 517 518 # Compute QoIs 519 kinematics_post = Kinematics(geo, kinematics.u) 520 521 # Save 522 output_results_paraview( 523 output_dir, 524 kinematics_post, 525 mod_repr, 526 name="solved" 527 ) 528 529 # 530 # Compute L-curve relevant quantities 531 # 532 _, pure_obj_form, reg_form = objective_info.get_objective_forms( 533 geo, 534 kinematics_target, 535 kinematics_sim, 536 mod_repr, 537 None 538 ) 539 540 pure_obj = assemble(pure_obj_form) 541 if reg_form is not None: 542 reg = assemble(reg_form) 543 else: 544 reg = 0.0 545 546 # 547 # End 548 # 549 # Timer end 550 total_time = timer.end() 551 logger.info(f"Inverse modeling procedure complete") 552 logger.info(f"Took {total_time} seconds to complete") 553 554 # Tear down logging 555 logger_destructor() 556 557 return total_time, pure_obj, reg 558 559 560def inverse(): 561 """The function invoked by the command. Parses arguments and passes 562 to `main`. 563 """ 564 parser = get_common_parser( 565 description="One stage of the inverse model to determine modulus from " 566 "displacements" 567 ) 568 569 add_objective_arguments(parser) 570 571 parser.add_argument( 572 "-r", 573 type=str, 574 metavar="RESULTS_DIR", 575 default="results", 576 help="superdirectory in which all results for given target u are stored" 577 ) 578 parser.add_argument( 579 "-t", 580 type=str, 581 metavar="TARGET_U_XDMF", 582 default=None, 583 help="filename with full-shape representation of target displacements" 584 ) 585 parser.add_argument( 586 "-a", 587 type=float, 588 metavar="TOL", 589 default=1e-8, 590 help="tolerance for inverse model" 591 ) 592 parser.add_argument( 593 "-d", 594 "--debug", 595 action="store_true", 596 help="produces output of pyadjoint tree and performs Taylor test" 597 ) 598 parser.add_argument( 599 "-m", 600 type=int, 601 metavar="MAX_ITER", 602 default=250, 603 help="maximum L-BFGS iterations" 604 ) 605 parser.add_argument( 606 "-i", 607 type=str, 608 metavar="INIT_STRAT", 609 default="zero", 610 help="what to initialize the modulus representation, i.e. beta, to" 611 ) 612 parser.add_argument( 613 "--no-restrict-ctl-dofs-to-veh", 614 action="store_true", 615 help="solve for the modulus everywhere in the domain instead" 616 ) 617 parser.add_argument( 618 "--opt-backend", 619 type=str, 620 metavar="BACKEND", 621 choices=["scipy", "moola"], 622 default="moola", 623 help="scipy: bounded, vector repr.; moola: unbounded, L2 repr." 624 ) 625 parser.add_argument( 626 "--mu", 627 type=float, 628 metavar="MU_FF", 629 default=108.0, 630 help="far-field shear modulus from rheometry" 631 ) 632 633 args = parser.parse_args() 634 635 if args.t is None: 636 args.t = os.path.join(args.c, "u_experiment.xdmf") 637 638 main(args) 639 640 641if __name__=="__main__": 642 inverse()
Write current guess to filesytem every (this many) L-BFGS iterations
Names of columns in inverse.csv
109def main(args): 110 """Sets up logging to relevant files and starts the inverse model. 111 112 * `args`: `argparse.Namespace` with parsed command arguments 113 114 Side-effects: see intro to `gel.scripts.inverse`; does all the file 115 handling except the experiment's subdirectory 116 117 Computation: calls `gel_inverse`, which handles the experiment's 118 subdirectory. 119 """ 120 logger = get_global_logger(LOG_FILE) 121 122 # Results 123 result_csv_path = os.path.join(args.r, "inverse.csv") 124 csv = ResultsCSV(result_csv_path, INVERSE_EXPERIMENT_COLS) 125 126 logger.info(f"Detected number of processes: {MPI.comm_world.Get_size()}") 127 128 objective_info = ObjectiveInfo(args) 129 130 exp_info = ( 131 args.r, 132 args.c, 133 args.t, 134 args.f, 135 args.k, 136 objective_info, 137 *args.b, 138 args.a, 139 args.i, 140 args.debug, 141 args.m, 142 args.l, 143 not args.no_restrict_ctl_dofs_to_veh, 144 args.p, 145 args.opt_backend, 146 args.bci, 147 args.bco, 148 args.mu, 149 args.u_init 150 ) 151 152 logger.info(f"Beginning experiment with arguments {exp_info}.") 153 154 # Be careful of it already existing 155 try: 156 try: 157 total_time, pure_obj, reg = gel_inverse(*exp_info) 158 159 # Update DataFrame 160 exp_info = _expand_obj_info(exp_info) 161 csv.add_row(list(exp_info) + [ 162 "Converged", 163 pure_obj, 164 reg, 165 float(total_time) 166 ]) 167 except RuntimeError as e: 168 logger.info( 169 f"FEniCS encountered an error. Recording this outcome" 170 ) 171 logger.info(e) 172 173 # Update DataFrame 174 exp_info = _expand_obj_info(exp_info) 175 csv.add_row(list(exp_info) + ["Crash", "", "", ""]) 176 177 # Save into file 178 csv.save() 179 except FileExistsError: 180 logger.info( 181 "Found that experiment directory already exists. Skipping..." 182 ) 183 except UnsupportedGeometryError: 184 logger.info( 185 "This geometry type is not supported by the inverse model at " 186 "this time. Skipping..." 187 )
Sets up logging to relevant files and starts the inverse model.
args:argparse.Namespacewith parsed command arguments
Side-effects: see intro to gel.scripts.inverse; does all the file
handling except the experiment's subdirectory
Computation: calls gel_inverse, which handles the experiment's
subdirectory.
190class UnsupportedGeometryError(Exception): 191 """Raised if `cell_data_dir` doesn't have a supported type.""" 192 pass
Raised if cell_data_dir doesn't have a supported type.
195def gel_inverse( 196 results_dir, 197 cell_data_dir, 198 target_file, 199 formulation, 200 k, 201 objective_info, 202 lower_bound, 203 upper_bound, 204 tol, 205 mod_repr_init, 206 debug=False, 207 maxiter=10000, 208 load_steps=2, 209 restrict_ctl_dofs_to_veh=False, 210 preconditioner="hypre_amg", 211 optimizer_backend="scipy", 212 bci=None, 213 bco=None, 214 mu=108.0, 215 u_init=None 216 ): 217 r"""Runs the inverse model, writes subdirectory and logs progress. 218 219 * `results_dir`: str path to directory to put all results 220 * `cell_data_dir`: str path to directory with geometry information 221 to use, see the constructor to `gel.geometry.Geometry` for required 222 files 223 * `target_file`: str path to full-shape .xdmf file with 224 displacements in "u" 225 * `formulation`: str a valid material model formulation, see 226 `gel.mechanics` for options 227 * `k`: float $\frac{D_1}{c_1}$ ratio 228 * `objective_info`: `gel.objective.ObjectiveInfo` with information 229 on the objective to be minimized 230 * `lower_bound`: float; either the lower bound for the "beta_tilde" 231 formulation described in `gel.mechanics` or the lower bound for 232 L-BFGS-B if the "scipy" backend if selected 233 * `upper_bound`: float; either the upper bound for the "beta_tilde" 234 formulation described in `gel.mechanics` or the upper bound for 235 L-BFGS-B if the "scipy" backend if selected 236 * `tol`: float tolerance for inverse model convergence 237 * `mod_repr_init`: str a valid modulus representation for the 238 initial guess, see `gel.mechanics` for options 239 * `debug`: bool, if True runs a Taylor test and outputs a tree of 240 the traced components of the simulation in file "graph.dot" in the 241 experiment's subdirectory 242 * `maxiter`: int maximum number of L-BFGS iterations 243 * `restrict_ctl_dofs_to_veh`: bool, it True will only control DoFs 244 in the volume within the event horizon with cutoff specified in 245 `objective_info` 246 * `preconditioner`: str preconditioner to use, see 247 `gel.gel.ForwardSimulation` for more details 248 * `optimizer_backend`: str moola for L-BFGS and that library, 249 otherwise scipy for L-BFGS-B and that library 250 * `bci`: str path to .vtk file with inner BC info, see 251 `gel.geometry.Geometry` for details 252 * `bco`: str path to .vtk file with outer BC info, see 253 `gel.geometry.Geometry` for details 254 * `mu`: float far-field shear modulus from rheometry 255 * `u_init`: str path to full-shape .xdmf file with initial 256 displacements 257 258 Side-effects: writes many files in new subdirectory to 259 `results_dir`, see intro to `gel.scripts.inverse` 260 261 Returns: 262 * float time it took to run 263 * float objective functional value $O$ at end (see `gel.objective`) 264 * float regularization functional value $R$ at end (see 265 `gel.objective`) 266 """ 267 # Input validation 268 validate_formulation(formulation) 269 validate_mod_repr_field(mod_repr_init) 270 271 # Get cell name 272 cell_name = read_cell_name(cell_data_dir) 273 # Get geometry type 274 geo_type = read_geo_type(cell_data_dir) 275 if geo_type != "real_cell_gel": 276 raise UnsupportedGeometryError(f"{geo_type} currently not supported") 277 278 # Output directory handling 279 output_dir = prepare_experiment_dir( 280 results_dir, 281 cell_name, 282 formulation, 283 restrict_ctl_dofs_to_veh, 284 mod_repr_init, 285 k, 286 objective_info.gamma_num, 287 objective_info.objective_type, 288 objective_info.regularization_type, 289 objective_info.objective_domain, 290 objective_info.regularization_domain, 291 objective_info.detectable_u_cutoff, 292 objective_info.apply_u_weight_to_reg, 293 objective_info.safe_u_weight_filename(), 294 lower_bound, 295 upper_bound, 296 tol, 297 preconditioner, 298 bci, 299 bco, 300 mu 301 ) 302 303 # Setup logging 304 logger, logger_destructor = create_experiment_logger(output_dir) 305 306 # 307 # Initial forward run 308 # 309 logger.info(f"Beginning inverse model in {formulation} formulation.") 310 logger.info(f"Using cell data from {cell_data_dir}, '{cell_name}'") 311 logger.info(f"Using mod_repr init strat {mod_repr_init}") 312 logger.info( 313 f"Restricting mod_ctl to VEH? {restrict_ctl_dofs_to_veh}" 314 ) 315 logger.info(f"Uses geometry type {geo_type}") 316 logger.info(f"Target file: {target_file}") 317 logger.info(f"Using D1/C1: {k}") 318 logger.info(f"Bounds: [{lower_bound}, {upper_bound}]") 319 logger.info(f"Using tolerance: {tol}") 320 logger.info(f"Using maximum iterations: {maxiter}") 321 logger.info(f"Using load steps: {load_steps}") 322 logger.info(f"Optimizer backend: {optimizer_backend}") 323 logger.info(f"Preconditioner: {preconditioner}") 324 logger.info(f"Override inner BC: {bci}") 325 logger.info(f"Override outer BC: {bco}") 326 logger.info(f"Far field modulus: {mu}") 327 logger.info(f"Initial displacements: {u_init}") 328 objective_info.log_info(logger) 329 330 # RESET TAPE 331 set_working_tape(Tape()) 332 333 # Timer start 334 timer = ExperimentTimer() 335 timer.start() 336 337 logger.info(f"Running forward model with initial guess...") 338 339 # Deal with telling geo about detectable regions, BCs 340 addn_args = {"bci_data" : bci, "bco_data" : bco} 341 if objective_info.detectable_u_cutoff is not None: 342 addn_args["u_magnitude_subdomains_file"] = target_file 343 addn_args["detectable_u_cutoff"] = objective_info.detectable_u_cutoff 344 345 formulation_kwargs = dict() 346 if formulation == "beta_tilde": 347 formulation_kwargs["beta_min"] = lower_bound 348 formulation_kwargs["beta_max"] = upper_bound 349 350 sim = ForwardSimulation( 351 geo_type, 352 k, 353 formulation=formulation, 354 mod_repr_init=mod_repr_init, 355 load_steps=load_steps, 356 vprint=logger.info, 357 mu_ff=mu, 358 data_directory=cell_data_dir, 359 restrict_ctl_dofs_to_veh=restrict_ctl_dofs_to_veh, 360 pc=preconditioner, 361 u_init=u_init, 362 formulation_kwargs=formulation_kwargs, 363 **addn_args 364 ) 365 366 sim.run_forward() 367 kinematics, mod_repr = sim.kinematics, sim.mod_repr # Pointers 368 369 ##################### 370 # Must deal with intercepted block 371 tape = get_working_tape() 372 solve_block = tape.get_blocks()[-1] 373 374 for block_var in solve_block.get_dependencies(): 375 if block_var.output == kinematics.u: 376 block_var.save_output() 377 solve_block.block_var_to_save = block_var 378 solve_block.prev_soln = Function(sim.geo.V) 379 ##################### 380 381 # Save 382 geo = sim.geo 383 logger.info( 384 f"Finished initial guess forward run. Saving results to {output_dir}" 385 ) 386 kinematics_sim = Kinematics(geo, kinematics.u) 387 output_results_paraview( 388 output_dir, 389 kinematics_sim, 390 mod_repr, 391 name="init_guess" 392 ) 393 394 # Read in target displacement 395 logger.info(f"Reading target displacements from {target_file}") 396 kinematics_target = kinematics_from_file(geo, target_file) 397 398 # Debug 399 if debug and rank == 0: 400 tape = get_working_tape() 401 tape.visualise(os.path.join(output_dir, "graph.dot")) 402 403 # 404 # Objective function construction 405 # 406 logger.info(f"Assembling inverse objective.") 407 408 pre_assembly, pure_obj_form, reg_form = objective_info.get_objective_forms( 409 geo, 410 kinematics_target, 411 kinematics_sim, 412 mod_repr, 413 logger 414 ) 415 416 # Finalize 417 obj = assemble(pre_assembly) 418 419 # 420 # Inverse model 421 # 422 # Create callback 423 i = 0 424 def callback(this_mod_repr): 425 nonlocal i, geo, kinematics, output_dir, mod_repr, solve_block 426 nonlocal optimizer_backend 427 428 if i % INTERM_SAVE_PERIOD == 0: 429 logger.info(f"Saving results for iteration {i}") 430 431 df.Function.assign( 432 kinematics.u, 433 solve_block.block_var_to_save.output 434 ) 435 kinematics_mid = Kinematics(geo, kinematics.u) 436 437 # Assign values to mod_repr 438 if optimizer_backend == "scipy": 439 dm = mod_repr.function_space().dofmap() 440 local_range = dm.ownership_range() 441 mod_repr.vector().set_local( 442 this_mod_repr[local_range[0]:local_range[1]] 443 ) 444 mod_repr.vector().apply("") 445 elif optimizer_backend == "moola": 446 mod_repr.assign(this_mod_repr.data) 447 448 # Save 449 output_results_paraview( 450 output_dir, 451 kinematics_mid, 452 mod_repr, 453 name="most_recent" 454 ) 455 456 i += 1 457 458 # Adjoint stuff, marking tape 459 control = Control(sim.ctl) 460 obj_hat = ReducedFunctional(obj, control) 461 462 # Check the derivative 463 if debug: 464 debug_deriv( 465 obj_hat, 466 geo, 467 mod_repr, 468 logger 469 ) 470 471 # Minimize 472 logger.info(f"Calling minimization routine...") 473 #if MPI.comm_world.rank == 0: 474 #breakpoint() 475 if optimizer_backend == "scipy": 476 mod_repr_opt = minimize( 477 obj_hat, 478 method = "L-BFGS-B", 479 tol=tol, 480 bounds = (lower_bound, upper_bound), 481 options = {"maxiter":maxiter,"gtol":tol,"disp": True}, 482 callback = callback 483 ) 484 elif optimizer_backend == "moola": 485 problem = MoolaOptimizationProblem(obj_hat) 486 mod_repr_moola = moola.DolfinPrimalVector(sim.ctl) 487 solver = moola.BFGS( 488 problem, 489 mod_repr_moola, 490 options={ 491 "jtol":tol, 492 "gtol":100*tol, 493 "Hinit":"default", 494 "maxiter":maxiter, 495 "mem_lim":10, 496 "display":2 497 }, 498 hooks={"after_iteration":callback} 499 ) 500 sol = solver.solve() 501 mod_repr_opt = sol["control"].data 502 else: 503 raise ValueError(f"Unknown optimizer backend {optimizer_backend}") 504 505 logger.info(f"Finished minimization") 506 507 # Assignments after optimal parameters are found 508 mod_repr.assign(mod_repr_opt) 509 510 # 511 # Post-pro: run forward of optimal params and save 512 # 513 logger.info(f"Running forward model with optimal mod_repr field...") 514 515 # Run forward model with modified mod_repr 516 sim.run_forward() 517 logger.info(f"Finished. Saving...") 518 519 # Compute QoIs 520 kinematics_post = Kinematics(geo, kinematics.u) 521 522 # Save 523 output_results_paraview( 524 output_dir, 525 kinematics_post, 526 mod_repr, 527 name="solved" 528 ) 529 530 # 531 # Compute L-curve relevant quantities 532 # 533 _, pure_obj_form, reg_form = objective_info.get_objective_forms( 534 geo, 535 kinematics_target, 536 kinematics_sim, 537 mod_repr, 538 None 539 ) 540 541 pure_obj = assemble(pure_obj_form) 542 if reg_form is not None: 543 reg = assemble(reg_form) 544 else: 545 reg = 0.0 546 547 # 548 # End 549 # 550 # Timer end 551 total_time = timer.end() 552 logger.info(f"Inverse modeling procedure complete") 553 logger.info(f"Took {total_time} seconds to complete") 554 555 # Tear down logging 556 logger_destructor() 557 558 return total_time, pure_obj, reg
Runs the inverse model, writes subdirectory and logs progress.
results_dir: str path to directory to put all resultscell_data_dir: str path to directory with geometry information to use, see the constructor togel.geometry.Geometryfor required filestarget_file: str path to full-shape .xdmf file with displacements in "u"formulation: str a valid material model formulation, seegel.mechanicsfor optionsk: float $\frac{D_1}{c_1}$ ratioobjective_info:gel.objective.ObjectiveInfowith information on the objective to be minimizedlower_bound: float; either the lower bound for the "beta_tilde" formulation described ingel.mechanicsor the lower bound for L-BFGS-B if the "scipy" backend if selectedupper_bound: float; either the upper bound for the "beta_tilde" formulation described ingel.mechanicsor the upper bound for L-BFGS-B if the "scipy" backend if selectedtol: float tolerance for inverse model convergencemod_repr_init: str a valid modulus representation for the initial guess, seegel.mechanicsfor optionsdebug: bool, if True runs a Taylor test and outputs a tree of the traced components of the simulation in file "graph.dot" in the experiment's subdirectorymaxiter: int maximum number of L-BFGS iterationsrestrict_ctl_dofs_to_veh: bool, it True will only control DoFs in the volume within the event horizon with cutoff specified inobjective_infopreconditioner: str preconditioner to use, seegel.gel.ForwardSimulationfor more detailsoptimizer_backend: str moola for L-BFGS and that library, otherwise scipy for L-BFGS-B and that librarybci: str path to .vtk file with inner BC info, seegel.geometry.Geometryfor detailsbco: str path to .vtk file with outer BC info, seegel.geometry.Geometryfor detailsmu: float far-field shear modulus from rheometryu_init: str path to full-shape .xdmf file with initial displacements
Side-effects: writes many files in new subdirectory to
results_dir, see intro to gel.scripts.inverse
Returns:
- float time it took to run
- float objective functional value $O$ at end (see
gel.objective) - float regularization functional value $R$ at end (see
gel.objective)
561def inverse(): 562 """The function invoked by the command. Parses arguments and passes 563 to `main`. 564 """ 565 parser = get_common_parser( 566 description="One stage of the inverse model to determine modulus from " 567 "displacements" 568 ) 569 570 add_objective_arguments(parser) 571 572 parser.add_argument( 573 "-r", 574 type=str, 575 metavar="RESULTS_DIR", 576 default="results", 577 help="superdirectory in which all results for given target u are stored" 578 ) 579 parser.add_argument( 580 "-t", 581 type=str, 582 metavar="TARGET_U_XDMF", 583 default=None, 584 help="filename with full-shape representation of target displacements" 585 ) 586 parser.add_argument( 587 "-a", 588 type=float, 589 metavar="TOL", 590 default=1e-8, 591 help="tolerance for inverse model" 592 ) 593 parser.add_argument( 594 "-d", 595 "--debug", 596 action="store_true", 597 help="produces output of pyadjoint tree and performs Taylor test" 598 ) 599 parser.add_argument( 600 "-m", 601 type=int, 602 metavar="MAX_ITER", 603 default=250, 604 help="maximum L-BFGS iterations" 605 ) 606 parser.add_argument( 607 "-i", 608 type=str, 609 metavar="INIT_STRAT", 610 default="zero", 611 help="what to initialize the modulus representation, i.e. beta, to" 612 ) 613 parser.add_argument( 614 "--no-restrict-ctl-dofs-to-veh", 615 action="store_true", 616 help="solve for the modulus everywhere in the domain instead" 617 ) 618 parser.add_argument( 619 "--opt-backend", 620 type=str, 621 metavar="BACKEND", 622 choices=["scipy", "moola"], 623 default="moola", 624 help="scipy: bounded, vector repr.; moola: unbounded, L2 repr." 625 ) 626 parser.add_argument( 627 "--mu", 628 type=float, 629 metavar="MU_FF", 630 default=108.0, 631 help="far-field shear modulus from rheometry" 632 ) 633 634 args = parser.parse_args() 635 636 if args.t is None: 637 args.t = os.path.join(args.c, "u_experiment.xdmf") 638 639 main(args)
The function invoked by the command. Parses arguments and passes
to main.