https://mooseframework.inl.gov
Loading...
Searching...
No Matches
SingleParameterInverseSolveAction.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
11
12#include "FEProblemBase.h"
13#include "Factory.h"
14#include "Control.h"
15#include "MooseUtils.h"
16
17registerMooseAction("OptimizationApp", SingleParameterInverseSolveAction, "add_multi_app");
18registerMooseAction("OptimizationApp", SingleParameterInverseSolveAction, "add_transfer");
19registerMooseAction("OptimizationApp", SingleParameterInverseSolveAction, "add_postprocessor");
20registerMooseAction("OptimizationApp", SingleParameterInverseSolveAction, "add_convergence");
21registerMooseAction("OptimizationApp", SingleParameterInverseSolveAction, "add_control");
22
25{
28 "Generates the full fixed-point inverse-solve workflow (forward sub-app, transfers, "
29 "postprocessors, convergence, and a secant or Newton inversion control) from a single "
30 "block.");
31
32 MooseEnum method("secant newton", "secant");
33 params.addParam<MooseEnum>("method", method, "Inversion update method.");
34 params.addRequiredParam<FileName>("forward_input", "Forward-model sub-app input file.");
35 params.addRequiredParam<PostprocessorName>(
36 "sub_parameter_postprocessor", "Sub-app postprocessor that receives the parameter value.");
37 params.addRequiredParam<PostprocessorName>(
38 "sub_output_postprocessor", "Sub-app postprocessor holding the output to match the target.");
39 params.addRequiredParam<FunctionName>("target_function", "Target output f(t).");
40 params.addParam<Real>("initial_parameter", 1.0, "Initial guess for the parameter.");
41 params.addRangeCheckedParam<Real>(
42 "perturbation",
43 1e-3,
44 "perturbation>0",
45 "Perturbation used to seed/compute the derivative (initial_delta for secant, "
46 "parameter_delta for newton).");
47 params.addRangeCheckedParam<Real>("absolute_tolerance",
48 1e-8,
49 "absolute_tolerance>0",
50 "Absolute tolerance on |output - target|.");
51 params.addRangeCheckedParam<Real>(
52 "relative_tolerance",
53 1e-6,
54 "relative_tolerance>0",
55 "Relative tolerance on |output - target|, relative to |target|.");
56 params.addParam<unsigned int>(
57 "max_iterations",
58 50,
59 "Maximum number of fixed-point iterations on the generated convergence. With method=newton "
60 "each Newton update consumes two fixed-point iterations (a base and a perturbed solve), so "
61 "the effective number of Newton updates is about half this value.");
62 params.addParam<bool>(
63 "accept_on_max_iterations",
64 false,
65 "If true, accept the current estimate when the fixed-point loop reaches max_iterations "
66 "instead of diverging, cutting the time step, and erroring.");
67 params.addParam<PostprocessorName>(
68 "result_postprocessor",
69 "inverse_parameter",
70 "Name of the created postprocessor holding the converged parameter (output to CSV).");
71 params.addParam<bool>(
72 "verbose",
73 false,
74 "Forwarded to the generated convergence: if true, it prints per-iteration convergence "
75 "information, including the accept-on-max decision when accept_on_max_iterations is set.");
76
77 return params;
78}
79
85
86void
88{
89 // Derive the generated-object name prefix in snake_case (e.g. single_parameter_inverse_solve).
90 const std::string p = MooseUtils::camelCaseToUnderscore(name());
91 const bool newton = getParam<MooseEnum>("method") == "newton";
92 const std::string residual = p + "_residual";
93 const PostprocessorName result = getParam<PostprocessorName>("result_postprocessor");
94
95 if (_current_task == "add_multi_app")
96 {
97 auto ps = _factory.getValidParams("TransientMultiApp");
98 ps.set<std::vector<FileName>>("input_files") = {getParam<FileName>("forward_input")};
99 // Load-bearing: the sub-app must re-solve on every fixed-point iteration (each begins a
100 // TIMESTEP_BEGIN), so the control sees a fresh forward output for each trial parameter.
101 ps.set<ExecFlagEnum>("execute_on") = {EXEC_TIMESTEP_BEGIN};
102 _problem->addMultiApp("TransientMultiApp", p + "_forward", ps);
103 }
104 else if (_current_task == "add_transfer")
105 {
106 auto t1 = _factory.getValidParams("MultiAppPostprocessorTransfer");
107 t1.set<MultiAppName>("to_multi_app") = p + "_forward";
108 t1.set<PostprocessorName>("from_postprocessor") = p + "_param";
109 t1.set<PostprocessorName>("to_postprocessor") =
110 getParam<PostprocessorName>("sub_parameter_postprocessor");
111 _problem->addTransfer("MultiAppPostprocessorTransfer", p + "_to_forward", t1);
112
113 auto t2 = _factory.getValidParams("MultiAppPostprocessorTransfer");
114 t2.set<MultiAppName>("from_multi_app") = p + "_forward";
115 t2.set<PostprocessorName>("from_postprocessor") =
116 getParam<PostprocessorName>("sub_output_postprocessor");
117 t2.set<PostprocessorName>("to_postprocessor") = p + "_output";
118 t2.set<MooseEnum>("reduction_type") = "average";
119 _problem->addTransfer("MultiAppPostprocessorTransfer", p + "_from_forward", t2);
120 }
121 else if (_current_task == "add_postprocessor")
122 {
123 const std::vector<OutputName> none = {"none"};
124
125 // Working parameter guess (transferred to the sub-app, read and written by the control).
126 auto pp = _factory.getValidParams("Receiver");
127 pp.set<Real>("default") = getParam<Real>("initial_parameter");
128 pp.set<std::vector<OutputName>>("outputs") = none;
129 _problem->addPostprocessor("Receiver", p + "_param", pp);
130
131 // Sub-app output (filled by the FROM transfer).
132 auto po = _factory.getValidParams("Receiver");
133 po.set<std::vector<OutputName>>("outputs") = none;
134 _problem->addPostprocessor("Receiver", p + "_output", po);
135
136 // Published converged parameter (the CSV output of interest).
137 auto pr = _factory.getValidParams("Receiver");
138 pr.set<Real>("default") = 0.0;
139 _problem->addPostprocessor("Receiver", result, pr);
140
141 // Convergence residual, written by the control each iteration (normalized so convergence is
142 // declared at a fixed tolerance of 1). Large default so a step is never "converged" pre-solve.
143 auto pres = _factory.getValidParams("Receiver");
144 pres.set<Real>("default") = 1e30;
145 pres.set<std::vector<OutputName>>("outputs") = none;
146 _problem->addPostprocessor("Receiver", residual, pres);
147 }
148 else if (_current_task == "add_convergence")
149 {
150 auto pc = _factory.getValidParams("PostprocessorConvergence");
151 pc.set<PostprocessorName>("postprocessor") = residual;
152 pc.set<Real>("tolerance") = 1.0;
153 pc.set<unsigned int>("max_iterations") = getParam<unsigned int>("max_iterations");
154 pc.set<bool>("converge_at_max_iterations") = getParam<bool>("accept_on_max_iterations");
155 pc.set<bool>("verbose") = getParam<bool>("verbose");
156 _problem->addConvergence("PostprocessorConvergence", p + "_convergence", pc);
157 }
158 else if (_current_task == "add_control")
159 {
160 // The action cannot enable the fixed-point loop itself (that is frozen when the executioner is
161 // constructed, before actions run), but by this task the executioner's convergence choice is
162 // finalized, so verify the user pointed it at this action's convergence. Otherwise the loop is
163 // disabled or driven by a different convergence and the inverse solve silently would not run.
164 const std::string conv_name = p + "_convergence";
165 if (!_problem->hasSetMultiAppFixedPointConvergenceName() ||
166 _problem->getMultiAppFixedPointConvergenceName() != conv_name)
167 {
168 // Distinguish three states for the error, since "set" alone conflates two very different
169 // user experiences: hasSetMultiAppFixedPointConvergenceName() is also true when the user
170 // wrote nothing and MOOSE silently defaulted the name
171 // (needToAddDefaultMultiAppFixedPointConvergence() is how that default path is told apart
172 // from the user actually naming a convergence). Each branch supplies its own complete
173 // trailing sentence, since the three cases do not share a sentence structure.
174 std::string current;
175 if (!_problem->hasSetMultiAppFixedPointConvergenceName())
176 // Reachable when the executioner never builds a FixedPointSolve at all (e.g. an
177 // Executor-based executioner), so no name -- default or otherwise -- was ever assigned.
178 current = "It is currently not set.";
179 else if (_problem->needToAddDefaultMultiAppFixedPointConvergence())
180 current = "No 'multiapp_fixed_point_convergence' was given, so MOOSE created a default "
181 "one instead.";
182 else
183 current =
184 "It is currently set to '" + _problem->getMultiAppFixedPointConvergenceName() + "'.";
185
186 mooseError("[",
187 p,
188 "] requires the executioner to use its generated convergence to enable and drive "
189 "the fixed-point loop. Add\n\n multiapp_fixed_point_convergence = ",
190 conv_name,
191 "\n\nto the [Executioner] block. ",
192 current);
193 }
194
195 const std::string ctype = newton ? "NewtonInversionControl" : "SecantInversionControl";
196 auto pctl = _factory.getValidParams(ctype);
197 pctl.set<PostprocessorName>("output_postprocessor") = p + "_output";
198 pctl.set<PostprocessorName>("parameter_postprocessor") = p + "_param";
199 pctl.set<PostprocessorName>("converged_parameter_postprocessor") = result;
200 pctl.set<PostprocessorName>("residual_postprocessor") = residual;
201 pctl.set<FunctionName>("target_function") = getParam<FunctionName>("target_function");
202 pctl.set<Real>("absolute_tolerance") = getParam<Real>("absolute_tolerance");
203 pctl.set<Real>("relative_tolerance") = getParam<Real>("relative_tolerance");
204 if (newton)
205 pctl.set<Real>("parameter_delta") = getParam<Real>("perturbation");
206 else
207 pctl.set<Real>("initial_delta") = getParam<Real>("perturbation");
208
209 pctl.addPrivateParam<FEProblemBase *>("_fe_problem_base", _problem.get());
210 auto control = _factory.create<Control>(ctype, p + "_control", pctl);
211 _problem->getControlWarehouse().addObject(control);
212 }
213}
const Real p
const ExecFlagType EXEC_TIMESTEP_BEGIN
registerMooseAction("OptimizationApp", SingleParameterInverseSolveAction, "add_multi_app")
static InputParameters validParams()
std::shared_ptr< FEProblemBase > & _problem
const std::string & _current_task
std::shared_ptr< MooseObject > create(const std::string &obj_name, const std::string &name, const InputParameters &parameters, THREAD_ID tid=0, bool print_deprecated=true)
InputParameters getValidParams(const std::string &name) const
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
T & set(const std::string &name, bool quiet_mode=false)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const std::string & name() const
void mooseError(Args &&... args) const
Factory & _factory
Generates the full fixed-point inverse-solve workflow for a single scalar parameter from one SinglePa...
SingleParameterInverseSolveAction(const InputParameters &parameters)
std::string camelCaseToUnderscore(const std::string &camel_case_name)