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

Write current guess to filesytem every (this many) L-BFGS iterations

LOG_FILE = 'global_log.txt'
INVERSE_EXPERIMENT_COLS = ['Results Dir', 'Cell Name', 'target_file', 'Formulation', 'k', 'gamma', 'objective_type', 'regularization_type', 'objective_domain', 'regularization_domain', 'detectable_u_cutoff', 'u_weight_filename', 'apply_u_weight_to_reg', 'lower_bound', 'upper_bound', 'tol', 'mod_repr_init', 'debug_mode', 'max_iter', 'load_steps', 'restrict_ctl_dofs_to_veh', 'preconditioner', 'optimizer_backend', 'bci', 'bco', 'outcome', 'pure_obj', 'reg', 'time']

Names of columns in inverse.csv

def main(args):
107def main(args):
108    """Sets up logging to relevant files and starts the inverse model.
109
110    * `args`: `argparse.Namespace` with parsed command arguments
111
112    Side-effects: see intro to `gel.scripts.inverse`; does all the file
113    handling except the experiment's subdirectory
114
115    Computation: calls `gel_inverse`, which handles the experiment's
116    subdirectory.
117    """
118    logger = get_global_logger(LOG_FILE)
119
120    # Results
121    result_csv_path = os.path.join(args.r, "inverse.csv")
122    csv = ResultsCSV(result_csv_path, INVERSE_EXPERIMENT_COLS)
123
124    logger.info(f"Detected number of processes: {MPI.comm_world.Get_size()}")
125
126    objective_info = ObjectiveInfo(args)
127
128    exp_info = (
129        args.r,
130        args.c,
131        args.t,
132        args.f,
133        args.k,
134        objective_info,
135        *args.b,
136        args.a,
137        args.i,
138        args.debug,
139        args.m,
140        args.l,
141        not args.no_restrict_ctl_dofs_to_veh,
142        args.p,
143        args.opt_backend,
144        args.bci,
145        args.bco
146    )
147
148    logger.info(f"Beginning experiment with arguments {exp_info}.")
149
150    # Be careful of it already existing
151    try:
152        try:
153            total_time, pure_obj, reg = gel_inverse(*exp_info)
154
155            # Update DataFrame
156            exp_info = _expand_obj_info(exp_info)
157            csv.add_row(list(exp_info) + [
158                "Converged",
159                pure_obj,
160                reg,
161                float(total_time)
162            ])
163        except RuntimeError as e:
164            logger.info(
165                f"FEniCS encountered an error. Recording this outcome"
166            )
167            logger.info(e)
168
169            # Update DataFrame
170            exp_info = _expand_obj_info(exp_info)
171            csv.add_row(list(exp_info) + ["Crash", "", "", ""])
172
173        # Save into file
174        csv.save()
175    except FileExistsError:
176        logger.info(
177            "Found that experiment directory already exists. Skipping..."
178        )
179    except UnsupportedGeometryError:
180        logger.info(
181            "This geometry type is not supported by the inverse model at "
182            "this time. Skipping..."
183        )

Sets up logging to relevant files and starts the inverse model.

  • args: argparse.Namespace with 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.

class UnsupportedGeometryError(builtins.Exception):
186class UnsupportedGeometryError(Exception):
187    """Raised if `cell_data_dir` doesn't have a supported type."""
188    pass

Raised if cell_data_dir doesn't have a supported type.

def gel_inverse( results_dir, cell_data_dir, target_file, formulation, k, objective_info, lower_bound, upper_bound, tol, mod_repr_init, debug=False, maxiter=10000, load_steps=2, restrict_ctl_dofs_to_veh=False, preconditioner='hypre_amg', optimizer_backend='scipy', bci=None, bco=None):
191def gel_inverse(
192        results_dir,
193        cell_data_dir,
194        target_file,
195        formulation,
196        k,
197        objective_info,
198        lower_bound,
199        upper_bound,
200        tol,
201        mod_repr_init,
202        debug=False,
203        maxiter=10000,
204        load_steps=2,
205        restrict_ctl_dofs_to_veh=False,
206        preconditioner="hypre_amg",
207        optimizer_backend="scipy",
208        bci=None,
209        bco=None
210    ):
211    r"""Runs the inverse model, writes subdirectory and logs progress.
212
213    * `results_dir`: str path to directory to put all results
214    * `cell_data_dir`: str path to directory with geometry information
215    to use, see the constructor to `gel.geometry.Geometry` for required
216    files
217    * `target_file`: str path to full-shape .xdmf file with
218    displacements in "u"
219    * `formulation`: str a valid material model formulation, see
220    `gel.mechanics` for options
221    * `k`: float $\frac{D_1}{c_1}$ ratio
222    * `objective_info`: `gel.objective.ObjectiveInfo` with information
223    on the objective to be minimized
224    * `lower_bound`: float; either the lower bound for the "beta_tilde"
225    formulation described in `gel.mechanics` or the lower bound for
226    L-BFGS-B if the "scipy" backend if selected
227    * `upper_bound`: float; either the upper bound for the "beta_tilde"
228    formulation described in `gel.mechanics` or the upper bound for
229    L-BFGS-B if the "scipy" backend if selected
230    * `tol`: float tolerance for inverse model convergence
231    * `mod_repr_init`: str a valid modulus representation for the
232    initial guess, see `gel.mechanics` for options
233    * `debug`: bool, if True runs a Taylor test and outputs a tree of
234    the traced components of the simulation in file "graph.dot" in the
235    experiment's subdirectory
236    * `maxiter`: int maximum number of L-BFGS iterations
237    * `restrict_ctl_dofs_to_veh`: bool, it True will only control DoFs
238    in the volume within the event horizon with cutoff specified in
239    `objective_info`
240    * `preconditioner`: str preconditioner to use, see
241    `gel.gel.ForwardSimulation` for more details
242    * `optimizer_backend`: str moola for L-BFGS and that library,
243    otherwise scipy for L-BFGS-B and that library
244    * `bci`: str path to .vtk file with inner BC info, see
245    `gel.geometry.Geometry` for details
246    * `bco`: str path to .vtk file with outer BC info, see
247    `gel.geometry.Geometry` for details
248
249    Side-effects: writes many files in new subdirectory to
250    `results_dir`, see intro to `gel.scripts.inverse`
251
252    Returns:
253    * float time it took to run
254    * float objective functional value $O$ at end (see `gel.objective`)
255    * float regularization functional value $R$ at end (see
256    `gel.objective`)
257    """
258    # Input validation
259    validate_formulation(formulation)
260    validate_mod_repr_field(mod_repr_init)
261
262    # Get cell name
263    cell_name = read_cell_name(cell_data_dir)
264    # Get geometry type
265    geo_type = read_geo_type(cell_data_dir)
266    if geo_type != "real_cell_gel":
267        raise UnsupportedGeometryError(f"{geo_type} currently not supported")
268
269    # Output directory handling
270    output_dir = prepare_experiment_dir(
271        results_dir,
272        cell_name,
273        formulation,
274        restrict_ctl_dofs_to_veh,
275        mod_repr_init,
276        k,
277        objective_info.gamma_num,
278        objective_info.objective_type,
279        objective_info.regularization_type,
280        objective_info.objective_domain,
281        objective_info.regularization_domain,
282        objective_info.detectable_u_cutoff,
283        objective_info.apply_u_weight_to_reg,
284        objective_info.safe_u_weight_filename(),
285        lower_bound,
286        upper_bound,
287        tol,
288        preconditioner,
289        bci,
290        bco
291    )
292
293    # Setup logging
294    logger, logger_destructor = create_experiment_logger(output_dir)
295
296    #
297    # Initial forward run
298    #
299    logger.info(f"Beginning inverse model in {formulation} formulation.")
300    logger.info(f"Using cell data from {cell_data_dir}, '{cell_name}'")
301    logger.info(f"Using mod_repr init strat {mod_repr_init}")
302    logger.info(
303        f"Restricting mod_ctl to VEH? {restrict_ctl_dofs_to_veh}"
304    )
305    logger.info(f"Uses geometry type {geo_type}")
306    logger.info(f"Target file: {target_file}")
307    logger.info(f"Using D1/C1: {k}")
308    logger.info(f"Bounds: [{lower_bound}, {upper_bound}]")
309    logger.info(f"Using tolerance: {tol}")
310    logger.info(f"Using maximum iterations: {maxiter}")
311    logger.info(f"Using load steps: {load_steps}")
312    logger.info(f"Optimizer backend: {optimizer_backend}")
313    logger.info(f"Preconditioner: {preconditioner}")
314    logger.info(f"Override inner BC: {bci}")
315    logger.info(f"Override outer BC: {bco}")
316    objective_info.log_info(logger)
317
318    # RESET TAPE
319    set_working_tape(Tape())
320
321    # Timer start
322    timer = ExperimentTimer()
323    timer.start()
324
325    logger.info(f"Running forward model with initial guess...")
326    
327    # Deal with telling geo about detectable regions, BCs
328    addn_args = {"bci_data" : bci, "bco_data" : bco}
329    if objective_info.detectable_u_cutoff is not None:
330        addn_args["u_magnitude_subdomains_file"] = target_file
331        addn_args["detectable_u_cutoff"] = objective_info.detectable_u_cutoff
332
333    formulation_kwargs = dict()
334    if formulation == "beta_tilde":
335        formulation_kwargs["beta_min"] = lower_bound
336        formulation_kwargs["beta_max"] = upper_bound
337
338    sim = ForwardSimulation(
339        geo_type,
340        k,
341        formulation=formulation,
342        mod_repr_init=mod_repr_init,
343        load_steps=load_steps,
344        vprint=logger.info,
345        data_directory=cell_data_dir,
346        restrict_ctl_dofs_to_veh=restrict_ctl_dofs_to_veh,
347        pc=preconditioner,
348        formulation_kwargs=formulation_kwargs,
349        **addn_args
350    )
351
352    sim.run_forward()
353    kinematics, mod_repr = sim.kinematics, sim.mod_repr # Pointers
354
355    #####################
356    # Must deal with intercepted block
357    tape = get_working_tape()
358    solve_block = tape.get_blocks()[-1]
359
360    for block_var in solve_block.get_dependencies():
361        if block_var.output == kinematics.u:
362            block_var.save_output()
363            solve_block.block_var_to_save = block_var
364            solve_block.prev_soln = Function(sim.geo.V)
365    #####################
366
367    # Save
368    geo = sim.geo
369    logger.info(
370        f"Finished initial guess forward run. Saving results to {output_dir}"
371    )
372    kinematics_sim = Kinematics(geo, kinematics.u)
373    output_results_paraview(
374        output_dir,
375        kinematics_sim,
376        mod_repr,
377        name="init_guess"
378    )
379
380    # Read in target displacement
381    logger.info(f"Reading target displacements from {target_file}")
382    kinematics_target = kinematics_from_file(geo, target_file)
383
384    # Debug
385    if debug and rank == 0:
386        tape = get_working_tape()
387        tape.visualise(os.path.join(output_dir, "graph.dot"))
388    
389    #
390    # Objective function construction
391    #
392    logger.info(f"Assembling inverse objective.")
393
394    pre_assembly, pure_obj_form, reg_form = objective_info.get_objective_forms(
395        geo,
396        kinematics_target,
397        kinematics_sim,
398        mod_repr,
399        logger
400    )
401
402    # Finalize
403    obj = assemble(pre_assembly)
404
405    #
406    # Inverse model
407    #
408    # Create callback
409    i = 0
410    def callback(this_mod_repr):
411        nonlocal i, geo, kinematics, output_dir, mod_repr, solve_block
412        nonlocal optimizer_backend
413
414        if i % INTERM_SAVE_PERIOD == 0:
415            logger.info(f"Saving results for iteration {i}")
416
417            df.Function.assign(
418                kinematics.u,
419                solve_block.block_var_to_save.output
420            )
421            kinematics_mid = Kinematics(geo, kinematics.u)
422
423            # Assign values to mod_repr
424            if optimizer_backend == "scipy":
425                dm = mod_repr.function_space().dofmap()
426                local_range = dm.ownership_range()
427                mod_repr.vector().set_local(
428                    this_mod_repr[local_range[0]:local_range[1]]
429                )
430                mod_repr.vector().apply("")
431            elif optimizer_backend == "moola":
432                mod_repr.assign(this_mod_repr.data)
433
434            # Save
435            output_results_paraview(
436                output_dir,
437                kinematics_mid,
438                mod_repr,
439                name="most_recent"
440            )
441
442        i += 1
443
444    # Adjoint stuff, marking tape
445    control = Control(sim.ctl)
446    obj_hat = ReducedFunctional(obj, control)
447
448    # Check the derivative
449    if debug:
450        debug_deriv(
451            obj_hat,
452            geo,
453            mod_repr,
454            logger
455        )
456
457    # Minimize
458    logger.info(f"Calling minimization routine...")
459    #if MPI.comm_world.rank == 0:
460        #breakpoint()
461    if optimizer_backend == "scipy":
462        mod_repr_opt = minimize(
463            obj_hat,
464            method = "L-BFGS-B",
465            tol=tol,
466            bounds = (lower_bound, upper_bound),
467            options = {"maxiter":maxiter,"gtol":tol,"disp": True},
468            callback = callback
469        )
470    elif optimizer_backend == "moola":
471        problem = MoolaOptimizationProblem(obj_hat)
472        mod_repr_moola = moola.DolfinPrimalVector(sim.ctl)
473        solver = moola.BFGS(
474            problem,
475            mod_repr_moola,
476            options={
477                "jtol":tol,
478                "gtol":100*tol,
479                "Hinit":"default",
480                "maxiter":maxiter,
481                "mem_lim":10,
482                "display":2
483            },
484            hooks={"after_iteration":callback}
485        )
486        sol = solver.solve()
487        mod_repr_opt = sol["control"].data
488    else:
489        raise ValueError(f"Unknown optimizer backend {optimizer_backend}")
490
491    logger.info(f"Finished minimization")
492
493    # Assignments after optimal parameters are found
494    mod_repr.assign(mod_repr_opt)
495
496    #
497    # Post-pro: run forward of optimal params and save
498    #
499    logger.info(f"Running forward model with optimal mod_repr field...")
500
501    # Run forward model with modified mod_repr
502    sim.run_forward()
503    logger.info(f"Finished. Saving...")
504
505    # Compute QoIs
506    kinematics_post = Kinematics(geo, kinematics.u)
507
508    # Save
509    output_results_paraview(
510        output_dir,
511        kinematics_post,
512        mod_repr,
513        name="solved"
514    )
515
516    #
517    # Compute L-curve relevant quantities
518    #
519    _, pure_obj_form, reg_form = objective_info.get_objective_forms(
520        geo,
521        kinematics_target,
522        kinematics_sim,
523        mod_repr,
524        None
525    )
526
527    pure_obj = assemble(pure_obj_form)
528    if reg_form is not None:
529        reg = assemble(reg_form)
530    else:
531        reg = 0.0
532
533    #
534    # End
535    #
536    # Timer end
537    total_time = timer.end()
538    logger.info(f"Inverse modeling procedure complete")
539    logger.info(f"Took {total_time} seconds to complete")
540
541    # Tear down logging
542    logger_destructor()
543
544    return total_time, pure_obj, reg

Runs the inverse model, writes subdirectory and logs progress.

  • results_dir: str path to directory to put all results
  • cell_data_dir: str path to directory with geometry information to use, see the constructor to gel.geometry.Geometry for required files
  • target_file: str path to full-shape .xdmf file with displacements in "u"
  • formulation: str a valid material model formulation, see gel.mechanics for options
  • k: float $\frac{D_1}{c_1}$ ratio
  • objective_info: gel.objective.ObjectiveInfo with information on the objective to be minimized
  • lower_bound: float; either the lower bound for the "beta_tilde" formulation described in gel.mechanics or the lower bound for L-BFGS-B if the "scipy" backend if selected
  • upper_bound: float; either the upper bound for the "beta_tilde" formulation described in gel.mechanics or the upper bound for L-BFGS-B if the "scipy" backend if selected
  • tol: float tolerance for inverse model convergence
  • mod_repr_init: str a valid modulus representation for the initial guess, see gel.mechanics for options
  • debug: 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 subdirectory
  • maxiter: int maximum number of L-BFGS iterations
  • restrict_ctl_dofs_to_veh: bool, it True will only control DoFs in the volume within the event horizon with cutoff specified in objective_info
  • preconditioner: str preconditioner to use, see gel.gel.ForwardSimulation for more details
  • optimizer_backend: str moola for L-BFGS and that library, otherwise scipy for L-BFGS-B and that library
  • bci: str path to .vtk file with inner BC info, see gel.geometry.Geometry for details
  • bco: str path to .vtk file with outer BC info, see gel.geometry.Geometry for details

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)
def inverse():
547def inverse():
548    """The function invoked by the command. Parses arguments and passes
549    to `main`.
550    """
551    parser = get_common_parser(
552        description="One stage of the inverse model to determine modulus from "
553        "displacements"
554    )
555
556    add_objective_arguments(parser)
557
558    parser.add_argument(
559        "-r",
560        type=str,
561        metavar="RESULTS_DIR",
562        default="results",
563        help="superdirectory in which all results for given target u are stored"
564    )
565    parser.add_argument(
566        "-t",
567        type=str,
568        metavar="TARGET_U_XDMF",
569        default=None,
570        help="filename with full-shape representation of target displacements"
571    )
572    parser.add_argument(
573        "-a",
574        type=float,
575        metavar="TOL",
576        default=1e-8,
577        help="tolerance for inverse model"
578    )
579    parser.add_argument(
580        "-d",
581        "--debug",
582        action="store_true",
583        help="produces output of pyadjoint tree and performs Taylor test"
584    )
585    parser.add_argument(
586        "-m",
587        type=int,
588        metavar="MAX_ITER",
589        default=250,
590        help="maximum L-BFGS iterations"
591    )
592    parser.add_argument(
593        "-i",
594        type=str,
595        metavar="INIT_STRAT",
596        default="zero",
597        help="what to initialize the modulus representation, i.e. beta, to"
598    )
599    parser.add_argument(
600        "--no-restrict-ctl-dofs-to-veh",
601        action="store_true",
602        help="solve for the modulus everywhere in the domain instead"
603    )
604    parser.add_argument(
605        "--opt-backend",
606        type=str,
607        metavar="BACKEND",
608        choices=["scipy", "moola"],
609        default="moola",
610        help="scipy: bounded, vector repr.; moola: unbounded, L2 repr."
611    )
612
613    args = parser.parse_args()
614
615    if args.t is None:
616        args.t = os.path.join(args.c, "u_experiment.xdmf")
617
618    main(args)

The function invoked by the command. Parses arguments and passes to main.