<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 TUTORIAL 03 - Geometrical parametrization
Keywords
Step1:  3. Affine decomposition
In order to obtain an affine decomposition, we need to recast the problem on a fixed, parameter independent, reference domain, as follows
Step2:  4. Main program
4.1. Read the mesh for this problem
The mesh was generated by the data/generate_mesh.ipynb notebook.
Step3:  4.2. Create Finite Element space (Lagrange P1)
Step4:  4.3. Allocate an object of the Hole class
Step5:  4.4. Prepare reduction with a POD-Galerkin method
Step6:  4.5. Perform the offline phase
Step7:  4.6. Perform an online solve
Step8:  4.7. Perform an error analysis
Step9:  4.8. Perform a speedup analysis
<ASSISTANT_TASK:>
Python Code:
from dolfin import *
from rbnics import *
Explanation: TUTORIAL 03 - Geometrical parametrization
Keywords: geometrical parametrization
1. Introduction
This Tutorial introduces problems featuring a geometrical parametrization, by solving a thermal conduction problem on a parametrized computational domain $\Omega_o(\boldsymbol{\mu})$ shown below:
<img src="data/hole_1.png" width="35%"/>
The problem is characterized by three parameters. The parameters $\mu_0$ and $\mu_1$ are related to the shape of the central hole as shown in the picture above, and vary in the following intervals
$$
\mu_0\in[0.5,1.5] \quad \text{and}\quad \mu_1\in[0.5,1.5].
$$
The parameter $\mu_2$ is the Biot number, which allows to parametrize heat exchange with a surrounding exterior fluid (e.g., air) in the following interval
$$
\mu_2\in[0.01,1];
$$
the diffusion process on the external boundaries $\Gamma_{o, 5} \cup \Gamma_{o, 6} \cup \Gamma_{o, 7} \cup \Gamma_{o, 8}$ will be affected by this parameter.
The parameter vector $\boldsymbol{\mu}$ is thus given by 
$$
\boldsymbol{\mu} = (\mu_0, \mu_1, \mu_2)
$$
on the parameter domain
$$
\mathbb{P}=[0.5,1.5]^2\times[0.01,1].
$$
In order to obtain a faster approximation of the problem, and avoiding any remeshing, we pursue a model reduction by means of a POD-Galerkin reduced order method from a fixed reference domain.
2. Parametrized formulation
Let $u_o(\boldsymbol{\mu})$ be the temperature in the domain $\Omega_o(\boldsymbol{\mu})$.
We will directly provide a weak formulation for this problem
<center>for a given parameter $\boldsymbol{\mu}\in\mathbb{P}$, find $u_o(\boldsymbol{\mu})\in\mathbb{V}_o(\boldsymbol{\mu})$ such that</center>
$$a_o\left(u_o(\boldsymbol{\mu}),v_o;\boldsymbol{\mu}\right)=f_o(v_o;\boldsymbol{\mu})\quad \forall v_o\in\mathbb{V}_o(\boldsymbol{\mu})$$
where
the function space $\mathbb{V}_o(\boldsymbol{\mu})$ is defined as
$$
\mathbb{V}_o(\boldsymbol{\mu}) = H^1(\Omega(\boldsymbol{\mu})).
$$
Note that, in contrast to the previous tutorials, the function space is parameter dependent due to the shape variation. 
the parametrized bilinear form $a_o(\cdot, \cdot; \boldsymbol{\mu}): \mathbb{V}o(\boldsymbol{\mu}) \times \mathbb{V}_o(\boldsymbol{\mu}) \to \mathbb{R}$ is defined by
$$a_o(u_o,v_o;\boldsymbol{\mu}) = \int{\Omega_o(\boldsymbol{\mu})}\nabla u_o\cdot\nabla v_o \ d\boldsymbol{x} + \mu_2\left(\int_{\Gamma_{o,5}}u_o\,v_o \ ds + \int_{\Gamma_{o,6}}u_o\,v_o \ ds + \int_{\Gamma_{o,7}}u_o\,v_o \ ds + \int_{\Gamma_{o,8}}u_o\,v_o \ ds\right),$$
the parametrized linear form $f_o(\cdot; \boldsymbol{\mu}): \mathbb{V}o(\boldsymbol{\mu}) \to \mathbb{R}$ is defined by
$$f_o(v_o;\boldsymbol{\mu}) = \int{\Gamma_{o,1}(\boldsymbol{\mu})}v_o \ ds + \int_{\Gamma_{o,2}(\boldsymbol{\mu})}v_o \ ds + \int_{\Gamma_{o,3}(\boldsymbol{\mu})}v_o \ ds + \int_{\Gamma_{o,4}(\boldsymbol{\mu})}v_o \ ds.$$
End of explanation
@PullBackFormsToReferenceDomain()
@AffineShapeParametrization("data/hole_vertices_mapping.vmp")
class Hole(EllipticCoerciveProblem):
    # Default initialization of members
    def __init__(self, V, **kwargs):
        # Call the standard initialization
        EllipticCoerciveProblem.__init__(self, V, **kwargs)
        # ... and also store FEniCS data structures for assembly
        assert "subdomains" in kwargs
        assert "boundaries" in kwargs
        self.subdomains, self.boundaries = kwargs["subdomains"], kwargs["boundaries"]
        self.u = TrialFunction(V)
        self.v = TestFunction(V)
        self.dx = Measure("dx")(subdomain_data=subdomains)
        self.ds = Measure("ds")(subdomain_data=boundaries)
        self.subdomains = subdomains
        self.boundaries = boundaries
    # Return custom problem name
    def name(self):
        return "Hole"
    # Return theta multiplicative terms of the affine expansion of the problem.
    def compute_theta(self, term):
        mu = self.mu
        if term == "a":
            theta_a0 = 1.0
            theta_a1 = mu[2]
            return (theta_a0, theta_a1)
        elif term == "f":
            theta_f0 = 1.0
            return (theta_f0, )
        else:
            raise ValueError("Invalid term for compute_theta().")
    # Return forms resulting from the discretization of the affine expansion of the problem operators.
    def assemble_operator(self, term):
        u = self.u
        v = self.v
        dx = self.dx
        ds = self.ds
        if term == "a":
            a0 = inner(grad(u), grad(v)) * dx
            a1 = inner(u, v) * ds(5) + inner(u, v) * ds(6) + inner(u, v) * ds(7) + inner(u, v) * ds(8)
            return (a0, a1)
        elif term == "f":
            f0 = v * ds(1) + v * ds(2) + v * ds(3) + v * ds(4)
            return (f0, )
        elif term == "inner_product":
            x0 = u * v * dx + inner(grad(u), grad(v)) * dx
            return (x0,)
        else:
            raise ValueError("Invalid term for assemble_operator().")
Explanation: 3. Affine decomposition
In order to obtain an affine decomposition, we need to recast the problem on a fixed, parameter independent, reference domain, as follows:
Choose $\Omega = \Omega_o((\mu_0, \mu_1) \equiv (1, 1))$ as reference domain, which we generate through the generate_mesh notebook provided in the data folder.
Define a map $\boldsymbol{T}(\boldsymbol{\cdot}; \boldsymbol{\mu}): \Omega \to \Omega_o(\boldsymbol{\mu})$ to carry out a pull back of the problem from the parametrized domain $\Omega_o(\boldsymbol{\mu})$ to the reference one $\Omega$. Since we aim at obtaining an affine decomposition, the map $\boldsymbol{T}(\boldsymbol{\cdot}; \boldsymbol{\mu})$ should be affine in its first argument. This is possible by taking a partition of the reference domain in several triangular subdomains, such as the following one
<img src="data/hole_2.png" width="35%"/><br>
3. Define a reference function space $$\mathbb{V} = H^1(\Omega),$$ to which the pulled back solution
$$u(\boldsymbol{\cdot}; \boldsymbol{\mu}) = u_o(\boldsymbol{T}(\boldsymbol{\cdot}; \boldsymbol{\mu}); \boldsymbol{\mu})$$ belongs
4. Pull back the bilinear form $a_o(\cdot, \cdot; \boldsymbol{\mu})$ and linear form $f_o(\cdot; \boldsymbol{\mu})$ onto the reference domain $\Omega$ by change of variables. Note that, due to the definition of the map $\boldsymbol{T}(\boldsymbol{\cdot}; \boldsymbol{\mu})$, the pull back will be different from one subdomain to the other. Call
$$a(\cdot, \cdot; \boldsymbol{\mu}): \mathbb{V} \times \mathbb{V} \to \mathbb{R}$$
$$f(\cdot; \boldsymbol{\mu}): \mathbb{V} \to \mathbb{R}$$
the resulting forms (and note that their arguments are now defined on $\Omega$).
5. Solve the equivalent problem
<center>for a given parameter $\boldsymbol{\mu}\in\mathbb{P}$, find $u(\boldsymbol{\mu})\in\mathbb{V}$ such that</center>
$$a\left(u(\boldsymbol{\mu}),v;\boldsymbol{\mu}\right)=f(v;\boldsymbol{\mu})\quad \forall v\in\mathbb{V}$$
End of explanation
mesh = Mesh("data/hole.xml")
subdomains = MeshFunction("size_t", mesh, "data/hole_physical_region.xml")
boundaries = MeshFunction("size_t", mesh, "data/hole_facet_region.xml")
Explanation: 4. Main program
4.1. Read the mesh for this problem
The mesh was generated by the data/generate_mesh.ipynb notebook.
End of explanation
V = FunctionSpace(mesh, "Lagrange", 1)
Explanation: 4.2. Create Finite Element space (Lagrange P1)
End of explanation
problem = Hole(V, subdomains=subdomains, boundaries=boundaries)
mu_range = [(0.5, 1.5), (0.5, 1.5), (0.01, 1.0)]
problem.set_mu_range(mu_range)
Explanation: 4.3. Allocate an object of the Hole class
End of explanation
reduction_method = PODGalerkin(problem)
reduction_method.set_Nmax(20)
reduction_method.set_tolerance(1e-6)
Explanation: 4.4. Prepare reduction with a POD-Galerkin method
End of explanation
reduction_method.initialize_training_set(100)
reduced_problem = reduction_method.offline()
Explanation: 4.5. Perform the offline phase
End of explanation
online_mu = (0.5, 0.5, 0.01)
reduced_problem.set_mu(online_mu)
reduced_solution = reduced_problem.solve()
plot(reduced_solution, reduced_problem=reduced_problem)
Explanation: 4.6. Perform an online solve
End of explanation
reduction_method.initialize_testing_set(100)
reduction_method.error_analysis()
Explanation: 4.7. Perform an error analysis
End of explanation
# 8. Perform a speedup analysis
reduction_method.initialize_testing_set(100)
reduction_method.speedup_analysis()
Explanation: 4.8. Perform a speedup analysis
End of explanation
<END_TASK>