.. _gettingstarted: ================= Getting started ================= If you have a supported Python installation on your computer, you can install the package via .. code-block:: bash pip install scikit-fem[all] Specifying ``[all]`` includes ``meshio`` for mesh input/output, and ``matplotlib`` for simple visualizations. The minimal dependencies are ``numpy`` and ``scipy``. You can also install scikit-fem in `Google Colab `_ by executing .. code-block:: bash !pip install scikit-fem Step 1: Clarify the problem =========================== In this tutorial we solve the Poisson problem .. math:: \begin{aligned} -\Delta u &= f \quad && \text{in $\Omega$,} \\ u &= 0 \quad && \text{on $\partial \Omega$,} \end{aligned} where :math:`\Omega = (0, 1)^2` is a square domain and :math:`f(x,y)=\sin \pi x \sin \pi y`. The weak formulation reads: find :math:`u \in H^1_0(\Omega)` satisfying .. math:: \int_\Omega \nabla u \cdot \nabla v \,\mathrm{d}x = \int_\Omega fv\,\mathrm{d}x \quad \forall v \in H^1_0(\Omega). .. note:: :math:`H^1_0(\Omega)` is the space of functions that are zero on the boundary :math:`\partial \Omega` and have finite energy: the square integral of the first derivative is finite. Step 2: Express the forms as code ================================= Next we write the forms .. math:: a(u, v) = \int_\Omega \nabla u \cdot \nabla v \,\mathrm{d}x \quad \text{and} \quad L(v) = \int_\Omega f v \,\mathrm{d}x as source code. Each form is written as a function and decorated as follows: .. doctest:: >>> import skfem as fem >>> from skfem.helpers import dot, grad # helpers make forms look nice >>> @fem.BilinearForm ... def a(u, v, _): ... return dot(grad(u), grad(v)) .. doctest:: >>> import numpy as np >>> @fem.LinearForm ... def L(v, w): ... x, y = w.x # global coordinates ... f = np.sin(np.pi * x) * np.sin(np.pi * y) ... return f * v For more information see :ref:`forms`. Step 3: Create a mesh ===================== The default constructors of :class:`~skfem.mesh.Mesh` initialize a unit square: .. doctest:: >>> mesh = fem.MeshTri().refined(3) # refine thrice >>> mesh Number of elements: 128 Number of vertices: 81 Number of nodes: 81 Named boundaries [# facets]: left [8], bottom [8], right [8], top [8] .. plot:: from skfem import * MeshTri().refined(3).draw(boundaries=True) Step 4: Define a basis ====================== The mesh is combined with a finite element to form a global basis. Here we choose the piecewise-linear basis: .. doctest:: >>> Vh = fem.Basis(mesh, fem.ElementTriP1()) >>> Vh Number of elements: 128 Number of DOFs: 81 Size: 27648 B Step 5: Assemble the linear system ================================== Now everything is in place for the finite element assembly. The resulting matrix has the type ``scipy.sparse.csr_matrix`` and the load vector has the type ``ndarray``. .. doctest:: >>> A = a.assemble(Vh) >>> l = L.assemble(Vh) >>> A.shape (81, 81) >>> l.shape (81,) Step 6: Find boundary DOFs ========================== Setting boundary conditions requires finding the degrees-of-freedom (DOFs) on the boundary. Empty call to :meth:`~skfem.assembly.basis.AbstractBasis.get_dofs` matches all boundary DOFs. .. doctest:: >>> D = Vh.get_dofs() >>> D Number of nodal DOFs: 32 ['u'] Step 7: Eliminate boundary DOFs and solve ========================================= The boundary DOFs must be eliminated from the linear system :math:`Ax=l` to set :math:`u=0` on the boundary. This can be done using :func:`~skfem.utils.condense`. The output can be passed to :func:`~skfem.utils.solve` which is a simple wrapper to ``scipy`` sparse solver: .. doctest:: >>> x = fem.solve(*fem.condense(A, l, D=D)) >>> x.shape (81,) .. plot:: from skfem import * from skfem.visuals.matplotlib import * from skfem.helpers import dot, grad import numpy as np basis = Basis(MeshTri().refined(3), ElementTriP1()) a = BilinearForm(lambda u, v, _: dot(grad(u), grad(v))) L = LinearForm(lambda v, w: np.sin(np.pi * w.x[0]) * np.sin(np.pi * w.x[1]) * v) y = solve(*condense(a.assemble(basis), L.assemble(basis), D=basis.get_dofs())) ax = draw(basis) plot(basis, y, ax=ax, nrefs=2, colorbar=True, shading='gouraud') Step 8: Calculate error ======================= The exact solution is known to be .. math:: u(x, y) = \frac{1}{2 \pi^2} \sin \pi x \sin \pi y. Thus, it makes sense to verify that the error is small. .. doctest:: >>> @fem.Functional ... def error(w): ... x, y = w.x ... uh = w['uh'] ... u = np.sin(np.pi * x) * np.sin(np.pi * y) / (2. * np.pi ** 2) ... return (uh - u) ** 2 >>> str(round(error.assemble(Vh, uh=Vh.interpolate(x)), 9)) '1.069e-06'