Partial differential equations ============================== The Surfacefun package is capable of solving general variable-coefficient, second-order, linear, elliptic partial differential equations on open and closed surfaces. Consider such a PDE on a given surface :math:`\Gamma`, .. math:: \mathcal{L}_\Gamma u(\boldsymbol{x}) = f(\boldsymbol{x}), \qquad \boldsymbol{x} \in \Gamma, where :math:`\mathcal{L}_\Gamma` is an elliptic partial differential operator of the form .. math:: \mathcal{L}_\Gamma u(\boldsymbol{x}) = \sum_{i=1}^3 \sum_{j=1}^3 a_{ij}(\boldsymbol{x}) \, \partial_i^\Gamma \partial_j^\Gamma u(\boldsymbol{x}) + \sum_{i=1}^3 b_i(\boldsymbol{x}) \,\partial_i^\Gamma u(\boldsymbol{x}) + c(\boldsymbol{x}) u(\boldsymbol{x}), with smooth coefficients :math:`a_{ij}`, :math:`b_i`, and :math:`c`. Here, we identify :math:`\partial_1^\Gamma`, :math:`\partial_2^\Gamma`, :math:`\partial_3^\Gamma` with the Cartesian :math:`x`-, :math:`y`-, and :math:`z`-components of the surface gradient, respectively. If :math:`\Gamma` is not a closed surface, the PDE may also be subject to boundary conditions, e.g., :math:`u(\boldsymbol{x}) = g(\boldsymbol{x})` for :math:`\boldsymbol{x} \in \partial\Gamma` and some function :math:`g`. Partial differential operators in Surfacefun are specified as MATLAB structs with properties defining the coefficients on each term appearing in :math:`\mathcal{L}_\Gamma`. The coefficients on the second-order terms are specified by setting ``pdo.dxx``, ``pdo.dyy``, ``pdo.dzz``, ``pdo.dxy``, ``pdo.dyx``, ``pdo.dyz``, ``pdo.dzy``, ``pdo.dxz``, and ``pdo.dzx``. Coefficients on the first-order terms can be set via ``pdo.dx``, ``pdo.dy``, and ``pdo.dz``. The zeroth-order coefficient is specified via ``pdo.c``. Each coefficient may be a constant, a function handle, or a ``surfacefun``. For instance, the Laplace-Beltrami operator can be specified via .. code-block:: matlab pdo = []; pdo.dxx = 1; pdo.dyy = 1; pdo.dzz = 1; or more simply as .. code-block:: matlab pdo = []; pdo.lap = 1; This sets the coefficients on the terms :math:`\partial_x^\Gamma \partial_x^\Gamma`, :math:`\partial_y^\Gamma \partial_y^\Gamma`, and :math:`\partial_z^\Gamma \partial_z^\Gamma` to one and the rest to zero. Let's solve a simple Laplace--Beltrami problem on the surface of the sphere. Since the spherical harmonics are eigenfunctions of the Laplace--Beltrami operator on the sphere, we can construct a test problem analytically: .. code-block:: matlab % Make a sphere mesh p = 16; nref = 2; dom = surfacemesh.sphere(p+1, nref); % Construct the true solution and corresponding righthand side l = 3; m = 2; sol = spherefun.sphharm(l, m); sol = surfacefun(@(x,y,z) sol(x,y,z), dom); f = -l*(l+1)*sol; % Specify the partial differential operator to be Laplace-Beltrami pdo = []; pdo.lap = 1; To solve the PDE, we create a ``surfaceop``. The ``surfaceop`` object encapsulates the factorization and solution routines corresponding to the fast direct solver in [1]. .. code-block:: matlab L = surfaceop(dom, pdo, f); It turns out that the Laplace--Beltrami problem on a closed surface is rank deficient (by one). However, it is uniquely solvable under the mean-zero conditions .. math:: \int_\Gamma u = \int_\Gamma f = 0. We can impose this condition by setting .. code-block:: matlab L.rankdef = true; Now we can solve the PDE: .. code-block:: matlab u = L.solve(); plot(u) .. container:: output-image .. figure:: images/lb_sphere.png :width: 400px :align: center Let's check the error: .. code-block:: matlab norm(u-sol) .. container:: output-text .. raw:: html
ans =
6.209136661992651e-12
Surface PDEs on surfaces of arbitrary genus may be solved using ``surfaceop``.
For example, here is the solution to a variable-coefficient surface Helmholtz
equation on a genus-1 stellarator geometry:
.. code-block:: matlab
% Construct the stellarator geometry
p = 16; nu = 8; nv = 24;
dom = surfacemesh.stellarator(p+1, nu, nv);
% Variable-coefficient surface Helmholtz equation
pdo = [];
pdo.lap = 1;
pdo.c = @(x,y,z) 300*(1-z);
f = -1;
L = surfaceop(dom, pdo, f);
u = L.solve();
plot(u), colorbar
.. container:: output-image
.. figure:: images/helmholtz.png
:width: 450px
:align: center
Now let's solve a problem on an open surface. We'll create an open surface by
extracting a subset of the patches from a closed surface:
.. code-block:: matlab
rng(0)
p = 16;
nref = 2;
dom = surfacemesh.blob(p+1, nref);
dom = surfacemesh(dom.x(1:16), dom.y(1:16), dom.z(1:16));
plot(dom), view(-110, 30), camlight
.. container:: output-image
.. figure:: images/open_surface.png
:width: 250px
:align: center
We construct a ``surfaceop`` on an open surface in the same way as on a closed
surface, except now the ``L.solve()`` method requires Dirichlet boundary data to
be passed as an argument:
.. code-block:: matlab
% Specify the righthand side and Dirichlet boundary data
f = -1;
bc = 0;
pdo = [];
pdo.lap = 1;
L = surfaceop(dom, pdo, f);
u = L.solve(bc);
plot(u), view(-110, 30), colorbar
.. container:: output-image
.. figure:: images/open_sol.png
:width: 375px
:align: center
Modifying an existing ``surfaceop``
-----------------------------------
A ``surfaceop`` is a direct solver for the specified surface PDE. This means
that once a factorization of the problem is constructed, the factorization may
be reused to solve the same PDE with different righthand sides and boundary data
in a manner that is more efficient than creating a new ``surfaceop`` again and
again.
To this end, a ``surfaceop`` may be factorized before it is given any data. This
is performed implicitly when ``L.solve()`` is called, but may be performed
explicitly by calling ``L.build()``:
.. code-block:: matlab
L = surfaceop(dom, pdo);
L.build();
The ``surfaceop`` can now be given any righthand side and will efficiently
update its factorization accordingly:
.. code-block:: matlab
L.rhs = @(x,y,z) sin(x.*y);
u = L.solve(bc);
plot(u), view(-110, 30), colorbar
.. container:: output-image
.. figure:: images/open_sol2.png
:width: 375px
:align: center
The ``surfaceop`` object is agnostic to boundary data. This means that an
existing solver may be used with any Dirichlet boundary data by simply passing
it to ``L.solve()``:
.. code-block:: matlab
bc = @(x,y,z) z;
u = L.solve(bc);
plot(u), view(-110, 30), colorbar
.. container:: output-image
.. figure:: images/open_sol3.png
:width: 375px
:align: center