Commit ed8a04a4 authored by Tim Keil's avatar Tim Keil

[docs/tutorial] unify white spaces

parent 50dfc05c
......@@ -36,7 +36,6 @@ reduced objective functional :math:`\mathcal{J}(\mu):= J(u_{\mu}, \mu)`
leading to the equivalent problem: Find a solution of
.. math::
\min_{\mu \in \mathcal{P}} \mathcal{J}(\mu). \tag{$\hat{P}$}
......@@ -51,6 +50,7 @@ In this tutorial, we use a simple linear scalar valued objective functional
and an elliptic primal equation to compare different approaches that solve
(:math:`\hat{P}`).
An elliptic model problem with a linear objective functional
------------------------------------------------------------
......@@ -59,7 +59,6 @@ We consider a domain :math:`\Omega:= [-1, 1]^2`, a parameter set
.. math::
- \nabla \cdot \big( \lambda(\mu) \nabla u_\mu \big) = l
with data functions
......@@ -96,7 +95,6 @@ equation. Moreover, we consider the linear objective functional
.. math::
\mathcal{J}(\mu) := \theta_{\mathcal{J}}(\mu)\, f_\mu(u_\mu)
where :math:`\theta_{\mathcal{J}}(\mu) := 1 + \frac{1}{5}(\mu_0 + \mu_1)`.
......@@ -129,7 +127,7 @@ With this data, we can construct a |StationaryProblem| in pyMOR.
diffusion = LincombFunction([rest_of_domain, indicator_domain], thetas)
theta_J = ExpressionParameterFunctional('1 + 1/5 * diffusion[0] + 1/5 * diffusion[1]', parameters,
derivative_expressions={'diffusion': ['1/5','1/5']})
derivative_expressions={'diffusion': ['1/5','1/5']})
problem = StationaryProblem(domain, f, diffusion, outputs=[('l2', f * theta_J)])
......@@ -137,7 +135,9 @@ We now use pyMOR's builtin discretization toolkit (see :doc:`tutorial_builtin_di
to construct a full order |StationaryModel|. Since we intend to use a fixed
energy norm
.. math:: \|\,.\|_{\bar{\mu}} : = a_{\,\bar{\mu}}(.,.),
.. math::
\|\,.\|_{\bar{\mu}} : = a_{\,\bar{\mu}}(.,.),
we also define :math:`\bar{\mu}`, which we pass via the argument
``mu_energy_product``. Also, we define the parameter space
......@@ -150,14 +150,13 @@ we also define :math:`\bar{\mu}`, which we pass via the argument
fom, data = discretize_stationary_cg(problem, diameter=1/50, mu_energy_product=mu_bar)
parameter_space = fom.parameters.space(0, np.pi)
We now define a function for the output of the model that can be used by the minimizer below.
.. jupyter-execute::
def fom_objective_functional(mu):
return fom.output(mu)[0]
We also pick a starting parameter for the optimization method,
which in our case is :math:`\mu^0 = (0.25,0.5)`.
......@@ -175,13 +174,10 @@ Next, we visualize the diffusion function :math:`\lambda_\mu` by using
diff = InterpolationOperator(data['grid'], problem.diffusion).as_vector(fom.parameters.parse(initial_guess))
fom.visualize(diff)
.. jupyter-execute::
print(data['grid'])
We can see that our FOM model has 20201 DoFs which just about suffices
to resolve the data structure in the diffusion. This suggests to use an
even finer mesh. However, for enabling a faster runtime for this
......@@ -238,8 +234,6 @@ Now, we can visualize the objective functional on the parameter space
YY = XX
plot_3d_surface(fom_objective_functional, XX, YY)
Taking a closer look at the functional, we see that it is at least
locally convex with a locally unique minimum. In general, however,
......@@ -282,6 +276,7 @@ helpful functions for recording and reporting the results.
print(' model enrichments: {}'.format(data['enrichments']))
print('')
Optimizing with the FOM using finite differences
------------------------------------------------
......@@ -314,13 +309,10 @@ primal equation. Here, we use this approach for a simple demonstration.
reference_minimization_data['time'] = perf_counter()-tic
reference_mu = fom_result.x
.. jupyter-execute::
report(fom_result, reference_minimization_data)
Taking a look at the result, we see that the optimizer needs :math:`7`
iterations to converge, but actually needs :math:`27` evaluations of the
full order model. Obiously, this is related to the computation of the
......@@ -335,8 +327,6 @@ the chosen points during the minimization.
addplot_xy_point_as_bar(reference_plot, mu[0], mu[1])
Optimizing with the ROM using finite differences
------------------------------------------------
......@@ -355,7 +345,6 @@ estimation of the coerciviy constant.
coercivity_estimator = MinThetaParameterFunctional(fom.operator.coefficients, mu_bar)
The online efficiency of MOR methods most likely comes with a
rather expensive offline phase. For PDE-constrained optimization, however,
it is not meaningful to ignore the
......@@ -402,10 +391,6 @@ Next, we plot the chosen parameters.
mu = mu.to_numpy()
addplot_xy_point_as_bar(ax, mu[0], mu[1])
Analogously to above, we perform the same optimization method, but use
the resulting ROM objective functional.
......@@ -429,7 +414,6 @@ the resulting ROM objective functional.
options={'ftol': 1e-15, 'gtol': 5e-5})
RB_minimization_data['time'] = perf_counter()-tic
.. jupyter-execute::
report(rom_result, RB_minimization_data, reference_mu)
......@@ -463,7 +447,6 @@ FOM optimization, we visualize both of them in the following plot.
z_range=(reference_plot_mean_z_lim, reference_plot.get_zlim()[1]))
Computing the gradient of the objective functional
--------------------------------------------------
......@@ -482,7 +465,6 @@ For computing the gradient of the linear objective functional
.. math::
\begin{align} \label{gradient:sens} \tag{1}
d_{\mu_i} \mathcal{J}(\mu) = \partial_{\mu_i} J(u_{\mu}, \mu) + \partial_u J(u_{\mu}, \mu)[d_{\mu_i} u_{\mu}]
= \partial_{\mu_i} J(u_{\mu}, \mu) + J(d_{\mu_i} u_{\mu}, \mu)
......@@ -501,6 +483,7 @@ where :math:`r_\mu^{\text{pr}}` denotes the residual of the primal
equation, i.e.
.. math::
r_\mu^{\text{pr}(u)[v] := l_\mu(v) - a_\mu(u, v) &&\text{for all }v \in V
A major issue of this approach is that the computation of the
......@@ -514,7 +497,6 @@ functional
.. math::
\mathcal{L}(u, \mu, p) = J(u, \mu) + r_\mu^{\text{pr}}(u, p)
where :math:`p \in V` is the adjoint variable. Deriving optimality
......@@ -536,7 +518,6 @@ functional by
.. math::
\begin{align}
d_{\mu_i} \mathcal{J}(\mu) &= \partial_{\mu_i} J(u_{\mu}, \mu) + \partial_u J(u_{\mu}, \mu)[d_{\mu_i} u_{\mu}] \\
&= \partial_{\mu_i} J(u_{\mu}, \mu) + a_\mu(d_{\mu_i} u_{\mu}, p_\mu) \\
......@@ -569,9 +550,9 @@ In order to use the output for :func:`~scipy.optimize.minimize` we thus use the
return fom.output_d_mu(fom.parameters.parse(mu), return_array=True, use_adjoint=True)
opt_fom_minimization_data = {'num_evals': 0,
'evaluations' : [],
'evaluation_points': [],
'time': np.inf}
'evaluations' : [],
'evaluation_points': [],
'time': np.inf}
tic = perf_counter()
opt_fom_result = minimize(partial(record_results, fom_objective_functional, opt_fom_minimization_data),
initial_guess,
......@@ -584,26 +565,20 @@ In order to use the output for :func:`~scipy.optimize.minimize` we thus use the
# update the reference_mu because this is more accurate!
reference_mu = opt_fom_result.x
.. jupyter-execute::
report(opt_fom_result, opt_fom_minimization_data)
With respect to the FOM result with finite differences, we see that we
have a massive speed up by computing the gradient information properly.
Optimizing using a gradient in ROM
----------------------------------
Obviously, we can also include the gradient of the ROM version of the
output functional.
.. jupyter-execute::
def rom_gradient_of_functional(mu):
......@@ -627,14 +602,12 @@ output functional.
opt_rom_minimization_data['time'] = perf_counter()-tic
report(opt_rom_result, opt_rom_minimization_data, reference_mu)
The online phase is even slightly faster than before but the offline
phase is obviously still the same as before. We also conclude that the
ROM model eventually gives less speedup by using a better optimization
method for the FOM and ROM.
Beyond the traditional offline/online splitting: enrich along the path of optimization
----------------------------------------------------------------------------------------
......@@ -660,8 +633,6 @@ approach goes beyond the classical offline/online splitting of RB
methods since it entirely skips the offline phase. In the following
code, we will test this method.
.. jupyter-execute::
pdeopt_reductor = CoerciveRBReductor(
......@@ -696,29 +667,25 @@ With this definitions, we can start the optimization method.
.. jupyter-execute::
opt_along_path_minimization_data = {'num_evals': 0,
'evaluations' : [],
'evaluation_points': [],
'time': np.inf,
'enrichments': 0}
'evaluations' : [],
'evaluation_points': [],
'time': np.inf,
'enrichments': 0}
opt_dict = {}
tic = perf_counter()
opt_along_path_result = minimize(partial(record_results_and_enrich, rom_objective_functional,
opt_along_path_minimization_data, opt_dict),
initial_guess,
method='L-BFGS-B',
jac=partial(compute_gradient_with_opt_rom, opt_dict),
bounds=(ranges, ranges),
options={'ftol': 1e-15, 'gtol': 5e-5})
initial_guess,
method='L-BFGS-B',
jac=partial(compute_gradient_with_opt_rom, opt_dict),
bounds=(ranges, ranges),
options={'ftol': 1e-15, 'gtol': 5e-5})
opt_along_path_minimization_data['time'] = perf_counter()-tic
.. jupyter-execute::
report(opt_along_path_result, opt_along_path_minimization_data, reference_mu)
The computational time looks at least better than the FOM optimization
and we are very close to the reference parameter.
But we are following the exact same path than the
......@@ -726,6 +693,7 @@ FOM and thus we need to solve the FOM model as often as before
(due to the enrichments). The only computational time that we safe is the one
for the gradients since we compute the dual solutions with the ROM.
Adaptively enriching along the path
-----------------------------------
......@@ -743,9 +711,6 @@ in the greedy algorithm.
fom, product=fom.energy_product, coercivity_estimator=coercivity_estimator)
opt_rom = pdeopt_reductor.reduce()
.. jupyter-execute::
def record_results_and_enrich_adaptively(function, data, opt_dict, mu):
......@@ -777,10 +742,10 @@ in the greedy algorithm.
.. jupyter-execute::
opt_along_path_adaptively_minimization_data = {'num_evals': 0,
'evaluations' : [],
'evaluation_points': [],
'time': np.inf,
'enrichments': 0}
'evaluations' : [],
'evaluation_points': [],
'time': np.inf,
'enrichments': 0}
opt_dict = {'opt_rom': opt_rom}
tic = perf_counter()
opt_along_path_adaptively_result = minimize(partial(record_results_and_enrich_adaptively, rom_objective_functional,
......@@ -792,15 +757,10 @@ in the greedy algorithm.
options={'ftol': 1e-15, 'gtol': 5e-5})
opt_along_path_adaptively_minimization_data['time'] = perf_counter()-tic
.. jupyter-execute::
report(opt_along_path_adaptively_result, opt_along_path_adaptively_minimization_data, reference_mu)
Now, we actually only needed :math:`4` enrichments and ended up with an
approximation error of about ``1e-07`` while getting the highest speed up
amongst all methods that we have seen above. Note, however, that this is
......@@ -811,7 +771,6 @@ after converging. If this changes anything, the ROM tolerance ``atol``
was too large. To conclude, we once again
compare all methods that we have discussed in this notebook.
.. jupyter-execute::
print('FOM with finite differences')
......@@ -843,8 +802,6 @@ compare all methods that we have discussed in this notebook.
assert opt_along_path_adaptively_minimization_data['enrichments'] == 4
Conclusion and some general words about MOR methods for optimization
--------------------------------------------------------------------
......@@ -880,7 +837,6 @@ optimization problems, we refer to
`KMSOV <https://arxiv.org/abs/2006.09297>`__ where for the latter, pyMOR
has been used for the numerical experiments.
Download the code:
:jupyter-download:script:`tutorial_optimization`
:jupyter-download:notebook:`tutorial_optimization`
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment