Force Inversion Example: Point Loads

Background

The MOOSE optimization module provides a flexible framework for solving inverse optimization problems in MOOSE. This page is part of a set of examples for different types of inverse optimization problems.

Example 1: Point Loads

In this example we are parameterizing the heat source intensity at the locations indicated by the symbols in Figure 1 that will produce a temperature field that most closely matches the temperature measurements taken at the points indicated by the symbols. Dirichlet boundary conditions are applied to the entire boundary with T=300.

Figure 1: Meshed geometry with measurement () and parameterized point load () locations.

The temperature field with a known heat source is shown in Figure 2. This will be used as synthetic measurement data in the optimization example where the magnitude of the point sources () will be parameterized to best match the temperatures at the measurement locations ().

Figure 2: Synthetic temperature field for known heat sources for optimization example.

Main Application Input

Optimization problems are solved using the MultiApps system. The main application contains the optimization executioner and the sub-applications solve the forward and adjoint PDE. The main application input is shown in Listing 1.

Listing 1: Main application optimization input for point load parameterization shown in Figure 1

# DO NOT CHANGE THIS TEST
# this test is documented as an example in forceInv_pointLoads.md
# if this test is changed, the figures will need to be updated.
measurement_points = '0.5 0.28 0
   0.5 0.6 0
   0.5 0.8 0
   0.5 1.1 0'
measurement_values = '293 304 315 320'
[Optimization]
[]

[OptimizationReporter]
  type = GeneralOptimization
  objective_name = objective_value
  parameter_names = 'parameter_results'
  num_values = '3'
[]

[Reporters]
  [main]
    type = OptimizationData
    measurement_points = ${measurement_points}
    measurement_values = ${measurement_values}
  []
[]

[Executioner]
  type = Optimize
  tao_solver = taonls
  petsc_options_iname = '-tao_gttol -tao_max_it -tao_nls_pc_type -tao_nls_ksp_type'
  petsc_options_value = '1e-5 10 none cg'
  verbose = true
[]

[MultiApps]
  [forward]
    type = FullSolveMultiApp
    input_files = forward.i
    execute_on = "FORWARD"
  []
  [adjoint]
    type = FullSolveMultiApp
    input_files = adjoint.i
    execute_on = "ADJOINT"
  []
  [homogeneousForward]
    type = FullSolveMultiApp
    input_files = forward_homogeneous.i
    execute_on = "HOMOGENEOUS_FORWARD"
  []
[]

[Transfers]
  # FORWARD transfers
  [toForward_measument]
    type = MultiAppReporterTransfer
    to_multi_app = forward
    from_reporters = 'main/measurement_xcoord
                      main/measurement_ycoord
                      main/measurement_zcoord
                      main/measurement_time
                      main/measurement_values
                      OptimizationReporter/parameter_results'
    to_reporters = 'measure_data/measurement_xcoord
                    measure_data/measurement_ycoord
                    measure_data/measurement_zcoord
                    measure_data/measurement_time
                    measure_data/measurement_values
                    point_source/value'
  []
  [fromForward]
    type = MultiAppReporterTransfer
    from_multi_app = forward
    # Note: We are transferring the misfit values into main misfit
    from_reporters = 'measure_data/objective_value measure_data/misfit_values'
    to_reporters = 'OptimizationReporter/objective_value main/misfit_values'
  []

  # ADJOINT transfers
  #NOTE:  the adjoint variable we are transferring is actually the gradient
  [toAdjoint]
    type = MultiAppReporterTransfer
    to_multi_app = adjoint
    from_reporters = 'main/measurement_xcoord
                      main/measurement_ycoord
                      main/measurement_zcoord
                      main/measurement_time
                      main/misfit_values'
    to_reporters = 'misfit/measurement_xcoord
                    misfit/measurement_ycoord
                    misfit/measurement_zcoord
                    misfit/measurement_time
                    misfit/misfit_values'
  []
  [fromAdjoint]
    type = MultiAppReporterTransfer
    from_multi_app = adjoint
    from_reporters = 'gradient/adjoint'
    to_reporters = 'OptimizationReporter/grad_parameter_results'
  []

  # HESSIAN transfers.  Same as forward.
  [toHomoForward]
    type = MultiAppReporterTransfer
    multi_app = homogeneousForward
    direction = to_multiapp
    from_reporters = 'main/measurement_xcoord
                      main/measurement_ycoord
                      main/measurement_zcoord
                      main/measurement_time
                      main/measurement_values
                      OptimizationReporter/parameter_results'
    to_reporters = 'measure_data/measurement_xcoord
                    measure_data/measurement_ycoord
                    measure_data/measurement_zcoord
                    measure_data/measurement_time
                    measure_data/measurement_values
                    point_source/value'
  []
  [fromHomoForward]
    type = MultiAppReporterTransfer
    multi_app = homogeneousForward
    direction = from_multiapp
    # Note: We are transferring the simulation values into misfit
    # this has to be done when using general opt and homogenous forward.
    from_reporters = 'measure_data/simulation_values'
    to_reporters = 'main/misfit_values'
  []
[]

[Reporters]
  [optInfo]
    type = OptimizationInfo
  []
[]

[Outputs]
  csv = true
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

The main application runs the optimization executioner and transfers data from the optimization executioner back and forth to the sub-apps that are running the "forward" and "adjoint" simulations.

commentnote

Since no mesh or physics kernels are required on the main-app, we use the Optimization action to set up all of the nullkernels, empty mesh, etc. needed to get a MOOSE simulation to run.

The Optimize executioner block in Listing 2 , provides an interface with the PETSc TAO optimization library. The optimization algorithm is selected with "tao_solver" with specific solver options set with petsc_options. In this example the TAO Hessian based Newton linesearch algorithm is selected with taonls.

Listing 2: Main application Executioner block for point load parameterization shown in Figure 1

[Executioner]
  type = Optimize
  tao_solver = taonls
  petsc_options_iname = '-tao_gttol -tao_max_it -tao_nls_pc_type -tao_nls_ksp_type'
  petsc_options_value = '1e-5 10 none cg'
  verbose = true
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

The optimize executioner requires an OptimizationReporter. In this example the GeneralOptimization reporter is used. A GeneralOptimization reporter is used to transfer data between the optimization executioner and the transfers used for communicating with the sub-apps. The "objective_name" is used to name the reporter that holds the objective value to optimize. "parameter_names" are the list of parameters being controlled. A gradient reporter is automatically created for each parameter with a name that is "grad_" concatenated to the parmater name provided. The "num_values" specifies the number of parameters per group of parameter_names being controlled. In this case there is a single parameter being controlled and that parameter contains three values being controlled. These three values are the magnitude of the point loads being applied, shown by the symbols in Figure 1. "initial_condition" can be used to supply initial guesses for parameter values. Bounded optimization algorithms like bounded conjugate gradient (taobncg) will use the bounds supplied by "lower_bounds" and "upper_bounds". Unbounded solvers like taonls used in this example will not apply the bounds even if they are in the input file.

The "measurement_points" are the xyz coordinates of the measurement data and "measurement_values" are the values at each measurement point. For this small example, the measurement data are supplied in the input file but for larger sets of measurement data, there is the option to read these values from a csv file using "measurement_file" and "file_value".

Listing 3: Main application OptimizationReporter and Reporter block for point load parameterization shown in Figure 1

[OptimizationReporter]
  type = GeneralOptimization
  objective_name = objective_value
  parameter_names = 'parameter_results'
  num_values = '3'
[]
[Reporters]
  [main]
    type = OptimizationData
    measurement_points = ${measurement_points}
    measurement_values = ${measurement_values}
  []
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

The MultiApps block is shown in Listing 4. The optimization executioner utilizes FullSolveMultiApp sub-apps which, when the module is linked in, have special "execute_on" flags to allow the optimization executioner to control which sub-app is being solved. This allows TAO to call whichever sub-app it needs in order to compute the quantity needed. For instance, if TAO needs to recompute the objective function with a new set of parameters, it will execute the sub-app with the flag exectute_on=FORWARD. The sub-apps being set-up correspond to the type of optimization algorithm being used. Gradient free algorithms like Nelder-Mead (taonm) only require a forward sub-app. Gradient based optimization algorithms like conjugate gradient (taobncg) require a forward and adjoint sub-app. Hessian based optimization algorithms like Newton linesearch (taonls) requires a forward, adjoint and homogeneous forward sub-app.

Listing 4: Main application MultiApps block for point load parameterization shown in Figure 1

[MultiApps]
  [forward]
    type = FullSolveMultiApp
    input_files = forward.i
    execute_on = "FORWARD"
  []
  [adjoint]
    type = FullSolveMultiApp
    input_files = adjoint.i
    execute_on = "ADJOINT"
  []
  [homogeneousForward]
    type = FullSolveMultiApp
    input_files = forward_homogeneous.i
    execute_on = "HOMOGENEOUS_FORWARD"
  []
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

Transfers makes up the next section of the main input file. TAO interacts with the various sub-apps using the reporter system and the MultiAppReporterTransfer. The first two Transfers in Listing 5 communicate data with the forward sub-app. These Transfers are used by TAO to compute the objective. The first transfer [toForward_measument] communicates the measurement locations to the forward app object OptimizationData. This transfer can be avoided by placing the OptimizationData block on the forward solve input file instead of the main optimization input file. These are the locations where the objective function is computed by minimizing the difference between the simulated and experimental data at discrete points, see Inverse Optimization, Eq. (13). The second transfer [toForward] is used by TAO to control the parameter being optimized. In this case, [toForward] controls the 'value' reporter in a ConstantVectorPostprocessor on the forward-app which is then consumed by the ReporterPointSource dirac kernel to apply the point source loading. The third transfer, [fromForward] returns the simulation values at the measurement points from the forward-app to the main-app main Reporter which computes a new objective function for TAO.

Listing 5: Main application Transfers for point load parameterization shown in Figure 1

[Transfers]
  # FORWARD transfers
  [toForward_measument]
    type = MultiAppReporterTransfer
    to_multi_app = forward
    from_reporters = 'main/measurement_xcoord
                      main/measurement_ycoord
                      main/measurement_zcoord
                      main/measurement_time
                      main/measurement_values
                      OptimizationReporter/parameter_results'
    to_reporters = 'measure_data/measurement_xcoord
                    measure_data/measurement_ycoord
                    measure_data/measurement_zcoord
                    measure_data/measurement_time
                    measure_data/measurement_values
                    point_source/value'
  []
  [fromForward]
    type = MultiAppReporterTransfer
    from_multi_app = forward
    # Note: We are transferring the misfit values into main misfit
    from_reporters = 'measure_data/objective_value measure_data/misfit_values'
    to_reporters = 'OptimizationReporter/objective_value main/misfit_values'
  []

  # ADJOINT transfers
  #NOTE:  the adjoint variable we are transferring is actually the gradient
  [toAdjoint]
    type = MultiAppReporterTransfer
    to_multi_app = adjoint
    from_reporters = 'main/measurement_xcoord
                      main/measurement_ycoord
                      main/measurement_zcoord
                      main/measurement_time
                      main/misfit_values'
    to_reporters = 'misfit/measurement_xcoord
                    misfit/measurement_ycoord
                    misfit/measurement_zcoord
                    misfit/measurement_time
                    misfit/misfit_values'
  []
  [fromAdjoint]
    type = MultiAppReporterTransfer
    from_multi_app = adjoint
    from_reporters = 'gradient/adjoint'
    to_reporters = 'OptimizationReporter/grad_parameter_results'
  []

  # HESSIAN transfers.  Same as forward.
  [toHomoForward]
    type = MultiAppReporterTransfer
    multi_app = homogeneousForward
    direction = to_multiapp
    from_reporters = 'main/measurement_xcoord
                      main/measurement_ycoord
                      main/measurement_zcoord
                      main/measurement_time
                      main/measurement_values
                      OptimizationReporter/parameter_results'
    to_reporters = 'measure_data/measurement_xcoord
                    measure_data/measurement_ycoord
                    measure_data/measurement_zcoord
                    measure_data/measurement_time
                    measure_data/measurement_values
                    point_source/value'
  []
  [fromHomoForward]
    type = MultiAppReporterTransfer
    multi_app = homogeneousForward
    direction = from_multiapp
    # Note: We are transferring the simulation values into misfit
    # this has to be done when using general opt and homogenous forward.
    from_reporters = 'measure_data/simulation_values'
    to_reporters = 'main/misfit_values'
  []
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

The next set of Transfers in Listing 5 communicate reporter values between the main-app and adjoint sub-app to compute the gradient of the objective function with respect to the controllable parameter. The third transfer named [toAdjoint] sends the misfit data at each measurement point to an OptimizationData reporter on the adjoint-app which is then consumed by the ReporterPointSource dirackernel to apply the misfit source loading as described by the top equation in Inverse Optimization, Eq. (20). The fourth transfer named [fromAdjoint] retrieves the gradient from the adjoint-app for TAO. The sibling transfer system could have been used to transfer the misfit data directly from the forward solve to the adjoint solve.

The final set of Transfers in Listing 5 communicate reporter values between the main-app and the homogeneous forward sub-app to compute a matrix free Hessian. The homogeneous forward transfers, [toHomoForward_measument] [toHomoForward] and [fromHomoForward] transfer the same reporter data to the same objects types used by the forward-app. For force inversion, the homogeneous forward app solves the same kernels as the forward-app except the boundary conditions are homogenized, i.e. set to zero. The parameter being controlled in OptimizationReporter by the Optimize executioner for the homogeneous forward problem is the variation in the parameter which is determined by TAO in order to build up the required matrix free Hessian. In the Transfer from the homogenous forward sub-app Listing 6, the simulation_values are transferred into the misfit_values in the main application. This will be problem specific to the tpy eof optimization performed.

Listing 6: Main application Transfers block for homogenous forward sub-app shown in Figure 1

[Transfers]
  [fromHomoForward]
    type = MultiAppReporterTransfer
    multi_app = homogeneousForward
    direction = from_multiapp
    # Note: We are transferring the simulation values into misfit
    # this has to be done when using general opt and homogenous forward.
    from_reporters = 'measure_data/simulation_values'
    to_reporters = 'main/misfit_values'
  []
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

Iteration history of the optimization solve is output by the OptimizationInfo reporter as shown in Listing 7. This reporter tracks the objective function value, norm of the gradient, total number of sub-app evaluations, and the number of forward, adjoint, or Hessian iterations per optimization step.

Listing 7: Main application OptimizationInfo for outputting convergence information.

[Reporters]
  [optInfo]
    type = OptimizationInfo
  []
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/main.i)

Forward Sub-Application Input

The forward problem sub-app input file for the point load simulation shown in Figure 1 is given in Listing 8. Tao uses the solution of the forward-app to compute the objective function. Almost every object included in the forward-app input would be needed in a regular simulation of this system with point loads of a known magnitude. The point loads are applied by the ReporterPointSource Dirac kernel. The xyz coordinates and point load value used in the ReporterPointSource come from the reporters defined in the ConstantVectorPostprocessor vector postprocessor. The reporters in the ConstantVectorPostprocessor are over-written by the [toForward] transfer on the main-app each time Tao executes the forward-app. The values being transferred into the ConstantVectorPostprocessor are the values being optimized and are controlled by Tao.

Listing 8: Complete input file for executing the forward problem sub-app.

# DO NOT CHANGE THIS TEST
# this test is documented as an example in forceInv_pointLoads.md
# if this test is changed, the figures will need to be updated.
[Mesh]
  [gmg]
    type = GeneratedMeshGenerator
    dim = 2
    nx = 10
    ny = 10
    xmax = 1
    ymax = 1.4
  []
[]

[Variables]
  [temperature]
  []
[]

[Kernels]
  [heat_conduction]
    type = MatDiffusion
    variable = temperature
    diffusivity = thermal_conductivity
  []
[]

[DiracKernels]
  [pt]
    type = ReporterPointSource
    variable = temperature
    x_coord_name = 'point_source/x'
    y_coord_name = 'point_source/y'
    z_coord_name = 'point_source/z'
    value_name = 'point_source/value'
  []
[]

[BCs]
  [left]
    type = DirichletBC
    variable = temperature
    boundary = left
    value = 300
  []
  [right]
    type = DirichletBC
    variable = temperature
    boundary = right
    value = 300
  []
  [bottom]
    type = DirichletBC
    variable = temperature
    boundary = bottom
    value = 300
  []
  [top]
    type = DirichletBC
    variable = temperature
    boundary = top
    value = 300
  []
[]

[Materials]
  [steel]
    type = GenericConstantMaterial
    prop_names = thermal_conductivity
    prop_values = 5
  []
[]

[Executioner]
  type = Steady
  solve_type = NEWTON
  nl_abs_tol = 1e-6
  nl_rel_tol = 1e-8
  petsc_options_iname = '-pc_type'
  petsc_options_value = 'lu'
[]

[VectorPostprocessors]
  [point_source]
    type = ConstantVectorPostprocessor
    vector_names = 'x y z value'
    value = '0.2 0.7 0.4;
             0.2 0.56 1;
             0 0 0;
             -1000 120 500'
    execute_on = LINEAR
  []
  [vertical]
    type = LineValueSampler
    variable = 'temperature'
    start_point = '0.5 0 0'
    end_point = '0.5 1.4 0'
    num_points = 21
    sort_by = y
  []
[]

[Reporters]
  [measure_data]
    type = OptimizationData
    objective_name = objective_value
    variable = temperature
  []
[]

[Outputs]
  console = false
  file_base = 'forward'
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/forward.i)

The locations where the simulation values are compared to the measurement data is transferred from the main-app [toForward_measurement] transfer into the forward-app OptimizationData reporter. By specifying the "variable", the reporter will evaluate the simulated temperature at the measurement locations. The main-app [fromForward] transfer then gets the temperature values sampled by OptimizationData back to Tao and the objective function can then be computed.

Adjoint Sub-Application Input

The adjoint problem sub-app computes the adjoint solution of the forward problem for a point load applied at the measurement locations. The adjoint sub-app input file is given in Listing 9. The adjoint variable is used to compute the gradient needed by TAO. The magnitude of the point loads for the adjoint problem are the misfit given by the difference of the measurement and simulation data, as shown by Inverse Optimization, Eq. (20). The misfit is computed by the OptimizationReporter on the main-app and is transferred into the adjoint-app [OptimizationData] object using the main-app [toAdjoint] transfer. The ReporterPointSource consumes the OptimizationData and applies the point loads at the measurement points.

Listing 9: Complete input file for executing the adjoint problem sub-app.

# DO NOT CHANGE THIS TEST
# this test is documented as an example in forceInv_pointLoads.md
# if this test is changed, the figures will need to be updated.
[Mesh]
  type = GeneratedMesh
  dim = 2
  nx = 10
  ny = 10
  xmax = 1
  ymax = 1.4
[]

[Variables]
  [adjoint]
  []
[]

[Problem]
  extra_tag_vectors = 'ref'
[]
[AuxVariables]
  [residual_src]
  []
[]
[AuxKernels]
  [residual_src]
    type = TagVectorAux
    vector_tag = 'ref'
    v = 'adjoint'
    variable = 'residual_src'
  []
[]

[Variables]
  [adjoint]
  []
[]

[Kernels]
  [heat_conduction]
    type = MatDiffusion
    variable = adjoint
    diffusivity = thermal_conductivity
  []
[]

#-----every adjoint problem should have these two
[DiracKernels]
  [pt]
    type = ReporterPointSource
    variable = adjoint
    x_coord_name = misfit/measurement_xcoord
    y_coord_name = misfit/measurement_ycoord
    z_coord_name = misfit/measurement_zcoord
    value_name = misfit/misfit_values
    extra_vector_tags = 'ref'
  []
[]

[Reporters]
  [misfit]
    type = OptimizationData
  []
[]

[BCs]
  [left]
    type = DirichletBC
    variable = adjoint
    boundary = left
    value = 0
  []
  [right]
    type = DirichletBC
    variable = adjoint
    boundary = right
    value = 0
  []
  [bottom]
    type = DirichletBC
    variable = adjoint
    boundary = bottom
    value = 0
  []
  [top]
    type = DirichletBC
    variable = adjoint
    boundary = top
    value = 0
  []
[]

[Materials]
  [steel]
    type = GenericConstantMaterial
    prop_names = thermal_conductivity
    prop_values = 5
  []
[]

[Executioner]
  type = Steady
  solve_type = NEWTON
  nl_abs_tol = 1e-6
  nl_rel_tol = 1e-8
  petsc_options_iname = '-pc_type'
  petsc_options_value = 'lu'
[]

[VectorPostprocessors]
  [gradient]
    type = PointValueSampler
    points = '0.2 0.2 0
              0.7 0.56 0
              0.4 1 0'
    variable = adjoint
    sort_by = id
  []
[]

[Outputs]
  console = false
  exodus = false
  file_base = 'adjoint'
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/adjoint.i)

The misfit load applied at the first iteration step is given by the residual values shown in Figure 3. Homogeneous boundary conditions are applied in the adjoint problem. The solution to the adjoint problem for the first iteration is shown in Figure 4. The adjoint variable can be interpreted as the sensitivity of point load locations on the measurement values. Ideally, measurements would be taken at locations that are most sensitive to the point source locations, i.e. the measurement locations maximize the adjoint variable at the point sources.

Figure 3: Adjoint misfit load from the first optimization iteration.

Figure 4: Adjoint solution from the misfit load applied in Figure 3 for the first optimization iteration.

The purpose of the adjoint sub-app is to compute gradient needed by TAO. This gradient is given by

where is the objective function, , is the controllable parameter, is the adjoint variable computed by the adjoint sub-app and is the residual from the forward problem. This gradient ignores regularization shown in Inverse Optimization, Eq. (2) which isn't needed for force inversion problems. For the simple case of point loading, the derivative of the forward problem residual with respect to the parameter, , given by Inverse Optimization, Eq. (25) is a projection matrix equal to 1 at locations where measurements are taken and zero everywhere else. The gradient given by is the adjoint variable sampled at the locations of the point loads. This sampling is done by the PointValueSampler vector postprocessor. The PointValueSampler is then transferred back to main-app in the fromAdjoint transfer and is the gradient used by TAO.

Homogeneous Forward Sub-Application Input

Hessian based optimization algorithms like Newton linesearch (taonls) require a homogeneous version of the forward problem given by the input file Listing 10. A homogenous forward problem is the forward problem with homogenous boundary conditions, i.e. the value or the flux is to zero. A homogenous forward problem is required for Hessian based optimization if a homogenous forward problem cannot be derived, then a gradient based algorithm like conjugate gradient (taobncg) should be used. The forward and homogeneous forward sub-apps have the same parameters transferred to them from the main-app. The parameters transferred into the homogeneous forward problem ConstantVectorPostprocessor vector postprocessor from the main-app [toHomoForward] transfer are perturbations of the parameters computed by TAO. The solution to the homogeneous forward problem is then sampled at the measurement points by OptimizationData and is returned to TAO on the main-app with the [fromHomoForward] transfer to be used as a matrix free Hessian. The matrix free Hessian algorithm is only valid for force inversion and does not work for material inversion. For linear optimization problems, Hessian based optimization algorithms are able to converge on the exact answer in a single iteration.

Listing 10: Complete input file for executing the homogeneous forward problem sub-app.

# DO NOT CHANGE THIS TEST
# this test is documented as an example in forceInv_pointLoads.md
# if this test is changed, the figures will need to be updated.
[Mesh]
  [gmg]
    type = GeneratedMeshGenerator
    dim = 2
    nx = 10
    ny = 10
    xmax = 1
    ymax = 1.4
  []
[]

[Variables]
  [temperature]
  []
[]

[Kernels]
  [heat_conduction]
    type = MatDiffusion
    variable = temperature
    diffusivity = thermal_conductivity
  []
[]

[DiracKernels]
  [pt]
    type = ReporterPointSource
    variable = temperature
    x_coord_name = 'point_source/x'
    y_coord_name = 'point_source/y'
    z_coord_name = 'point_source/z'
    value_name = 'point_source/value'
  []
[]

[BCs]
  [left]
    type = DirichletBC
    variable = temperature
    boundary = left
    value = 0
  []
  [right]
    type = DirichletBC
    variable = temperature
    boundary = right
    value = 0
  []
  [bottom]
    type = DirichletBC
    variable = temperature
    boundary = bottom
    value = 0
  []
  [top]
    type = DirichletBC
    variable = temperature
    boundary = top
    value = 0
  []
[]

[Materials]
  [steel]
    type = GenericConstantMaterial
    prop_names = thermal_conductivity
    prop_values = 5
  []
[]

[Executioner]
  type = Steady
  solve_type = NEWTON
  nl_abs_tol = 1e-6
  nl_rel_tol = 1e-8
  petsc_options_iname = '-pc_type'
  petsc_options_value = 'lu'
[]

[VectorPostprocessors]
  [point_source]
    type = ConstantVectorPostprocessor
    vector_names = 'x y z value'
    value = '0.2 0.7 0.4;
             0.2 0.56 1;
             0 0 0;
             -1000 120 500'
    execute_on = LINEAR
  []
[]

[Reporters]
  [measure_data]
    type = OptimizationData
    variable = temperature
  []
[]

[Outputs]
  console = false
  file_base = 'forward_homo'
[]
(modules/optimization/test/tests/optimizationreporter/point_loads/forward_homogeneous.i)

Optimization Results

The results for this example are shown in Figure 5. The left plot in Figure 5 shows the temperature along a vertical line passing through the measurement points is taken at several conjugate gradient iterations. The right plot shows the convergence of the objective function for several optimization algorithms. It's important to note when looking at the convergence plot that the Nelder Mead gradient free algorithm requires only a single forward solve per iteration where as the gradient based algorithms require at least two solves, one for the forward problem and one for the adjoint problem. The Hessian based algorithms require several forward, adjoint, and homogenous forward solves per iteration to make the matrix free Hessian accurate enough to converge. Even with this in mind, the Hessian based algorithm is the most computationally efficient for this problem. The taolmvm gradient based method also performs well and only requires an adjoint.

Figure 5: (Left) Temperature field taken along pink dashed line in Figure 1 at conjugate gradient iterations. (Right) Convergence of the objective function for different optimization algorithms.