diff --git a/.buildinfo b/.buildinfo index d56e401..a19252e 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 2fc1b2936d785829fdbc462f16cc33bc -tags: fbb0d17656682115ca4d033fb2f83ba1 +config: a38950419e00abfe3c37662591290520 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/_sources/advection_diffusion.rst.txt b/_sources/advection_diffusion.rst.txt new file mode 100644 index 0000000..4b9b7ae --- /dev/null +++ b/_sources/advection_diffusion.rst.txt @@ -0,0 +1,215 @@ +The advection-diffusion-reaction equation +----------------------------------------- + +The advection-diffusion-reaction equation (also called the continuity equation in semiconductor physics) in flux form, is given by, + +.. math:: + u_t = (\mathcal{-F})_x + s(t,x,u) + +where :math:`\mathcal{F} = au - du_x`. This expression holds over the whole domain, therefore it must also hold over a subdomain :math:`\Omega_j=(x_{j-1/2}, x_{j+1/2})`. Integrating over the subdomain, or cell, gives, + +.. math:: + \int_{x_{j-1/2}}^{x_{j+1/2}} u_t~dx = \int_{x_{j-1/2}}^{x_{j+1/2}} (\mathcal{-F})_x~dx + \int_{x_{j-1/2}}^{x_{j+1/2}} s(x,t,u)~dx + +If we now use :math:`w` and :math:`\bar{s}` to represent the cell averages of the solution variable and reaction term then we don't have to perform any actual integrations. The fundamental quantity we will use in the discretised equations is a cell average, + +.. math:: + w_j^{\prime} = -\frac{\mathcal{F}_{j+1/2}}{h_j} + \frac{\mathcal{F}_{j-1/2}}{h_{j}} + \bar{s}_j + +.. note:: + In the above we *can* perform the integration of the flux term. Integrating the divergence of the flux over the unit cell simply gives use the flux at the cell faces, :math:`\int_{x_{j-1/2}}^{x_{j+1/2}} (\mathcal{-F})_x~dx = \left[ \mathcal{-F} \right]_{x_{j-1/2}}^{x_{j+1/2}}`. Because we want the cell average we divide by the cell width :math:`h_j`. In three dimension we would divide by the volume of the cell, hence the name, finite volume method. + +For the advection component of the flux we will use a linear interpolation (see :ref:`label-interpolation-aside`) to determine the contribution at the cell faces, and for the diffusion component with will use a simple cell average, + +.. math:: + \mathcal{F}_{j+\frac{1}{2}} = a_{j+\frac{1}{2}}\left( \frac{h_{j+1}}{2h_{+}} w_j + \frac{h_j}{2h_{+}} w_{j+1} \right) - d_{j+\frac{1}{2}} \frac{w_{j+1}-w_j}{h_{+}} + +.. math:: + \mathcal{F}_{j-\frac{1}{2}} = a_{j-\frac{1}{2}}\left( \frac{h_{j}}{2h_{-}} w_{j-1} + \frac{h_{j-1}}{2h_{-}} w_{j} \right) - d_{j-\frac{1}{2}} \frac{w_{j}-w_{j-1}}{h_{-}} + +The meaning of the "h" terms is shown in the figure below, + +.. figure:: img/cells_FVM.png + :scale: 100 % + :alt: Finite volume cells with important distances and positions labelled. + :align: center + + Finite volume cells with important distances and positions labelled. + +Substituting the definition of the fluxes into the above equation yields, + +.. math:: + w_j^{\prime} = \frac{w_{j-1}}{h_j} \left( \frac{a(x_{j-\frac{1}{2}}) h_j}{2h_{-}} + \frac{d(x_{j-\frac{1}{2}})}{h_{-}}\right) + \frac{w_j}{h_j}\left( \frac{a(x_{j-\frac{1}{2}})h_{j-1}}{2h_{-}} - \frac{a(x_{j+\frac{1}{2}})h_{j+1}}{2h_{+}} - \frac{d(x_{j-\frac{1}{2}})}{h_{-}} - \frac{d(x_{j+\frac{1}{2}})}{h_{+}} \right) + \frac{w_{j+1}}{h_j} \left( \frac{-a(x_{j+\frac{1}{2}})h_j}{2h_{+}} + \frac{d(x_{j+\frac{1}{2}})}{h_{+}} \right) + :label: eq1 + +This equation is the *semi-discrete* form because the equation has not been discretised in time. + +For convenience we will simply write the r.h.s. of :eq:`eq1` as, + +.. math:: + w_j^{\prime} = F(w) + +where it is understood that :math:`w = (w_{j-1}, w_j, w_{j+1})` stands for the discretisation stencil. + +Adaptive upwinding & exponential fitting +**************************************** + +Hundsdorfer (on pg. 266) notes that a adaptive upwinding scheme can be introduced by altering the diffusion coefficient, + +.. math:: + d(x_{j}) \rightarrow d(x_{j}) + \frac{1}{2}\kappa_{j} h_{j} a(x_{j}) + + +.. math:: + \kappa=\text{sgn}(a(x_{j})). + +Here the nature of the discretisation is defined by the sign of the :math:`a`, when :math:`a\neq0` the scheme results in an upwind discretisation. Only if :math:`a=0` (corresponding to the diffusion equation limit) does the scheme become based on a central difference (see the red line in the Figure below). A generalisation is possible by choosing a function for :math:`\kappa` which automatically adjusts the discretisation method depending on the local value of Peclet's number, :math:`\mu=ah/d`. Exponential fitting has shown to be a robust scheme for steady-state approaches. Exponential fitting can be introduced by, + +.. math:: + \kappa = \frac{e^{\mu}+1}{e^{\mu}-1} - \frac{2}{\mu} + +An approximation to the above, which does not require the evaluation of exponentials is commonly used, + +.. math:: + \kappa = \begin{cases} + \text{max}(0, 1-2/\mu) & \text{when}~ \mu>0 \\ + \text{min}(0, -1-2/\mu) & \text{when}~ \mu<0 + \end{cases} + +.. figure:: img/Exponential_fitting_and_approximation.png + :scale: 100 % + :alt: Exponential fitting and approximation + :align: center + + +This is a very elegant want to introduce adaptive upwinding or exponential fitting because it can be brought into the discretisation in an ad hoc manner. Note that for all of the adaptive expressions :math:`\kappa\rightarrow\pm1` as :math:`\mu\rightarrow\pm\infty`, and :math:`\kappa\rightarrow 0` as :math:`\mu\rightarrow 0`. This means the discretisation will automatically weight in the favour of the upwind scheme in locations where advection dominates. Conversely, where diffusion dominates the weighting factor shifts in favour of a central difference scheme. + +Explicit and implicit forms +*************************** + +In the discussion so far we have been ignoring one important deal: at what *time* is the r.h.s of the discretised equation evaluated? + +Moreover, if we choose the r.h.s to be evaluated at the *present* time step, :math:`t_n` this is known as an *explicit* method, + +.. math:: + w_j^{\prime} = F(w^n) + +Explicit methods are very simple. Starting with initial conditions at time :math:`t_n`, the above equation can be rearranged to find the solution variable :math:`w_j^{n+1}` at the future time step, :math:`t_{n+1}`. + +However the downside of using explicit methods is that they are often numerically unstable unless the time step is exceptionally (sometime unrealistically) small. + +Fortunately there is an second alternative, we can choice to write the r.h.s of the equation at the *future* time step, :math:`t_{n+1}`, this is known as an *implicit* method, + +.. math:: + w_j^{\prime} = F(w^{n+1}) + +Explicit methods are significantly more numerically robust, however they pose a problem, how does one write the equations because the solution variable at the future time step :math:`w^{n+1}` is unknown? The answer is that at each time step we must solve a linear system of equation to find :math:`w^{n+1}`. + +.. note:: + For the **linear** advection-diffusion-reaction equation implicit methods are simply to implement even though the computation cost is increases. One must simply write the equation in the linear form :math:`A\cdot x = d` and solve for :math:`x` which is the solution variable at the future time step. However if the equations are non-linear then implicit methods pose problem because the equation **cannot** be written in linear form. In these situations are there are a number of techniques that are used but they all most use a iterative procedure, such as a Newton-Raphson method, to solve the equations. + +The following section assumes that the equation is linear. + +The :math:`\theta`-method +************************* + +The :math:`\theta`-method is an approach which combines implicit and explicit forms into one method. It consists of writing the equation as the average of the implicit and explicit forms. If we let :math:`F_{w^{n}}` and :math:`F_{w^{n+1}}` stand for the r.h.s of the explicit and implicit forms of the equations then the :math:`\theta`-method gives, + +.. math:: + w_j^{\prime} = \theta F(w_j^{n+1}) + (1-\theta)F(w_j^{n}) + +Setting :math:`\theta=0` recovers a fully implicit scheme while :math:`\theta=1` gives a fully explicit discretisation. The value of :math:`\theta` is not limited to just 0 or 1. It is common to set :math:`\theta=1/2`, this is called the Crank-Nicolson method. It is particularly popular for diffusion problem because it preserves the stability of the implicit form but also increases the accuracy of the time integration from first to second order (because two points in time are being averaged). For advection diffusion problems the Crank-Nicolson method is also unconditionally stable. + +In the above equation, + +.. math:: + F(w_j^{n}) = r_a w_{j-1}^{n} + r_b w_{j}^{n} + r_c w_{j+1}^{n} \\ + F(w_j^{n+1}) = r_a w_{j-1}^{n+1} + r_b w_{j}^{n+1} + r_c w_{j+1}^{n+1} + +and the coefficients are, + +.. math:: + r_a & = \frac{1}{h_j} \left( \frac{a(x_{j-\frac{1}{2}}) h_j}{2h_{-}} + \frac{d(x_{j-\frac{1}{2}})}{h_{-}}\right) \\ + r_b & = \frac{1}{h_j}\left( \frac{a(x_{j-\frac{1}{2}})h_{j-1}}{2h_{-}} - \frac{a(x_{j+\frac{1}{2}})h_{j+1}}{2h_{+}} - \frac{d(x_{j-\frac{1}{2}})}{h_{-}} - \frac{d(x_{j+\frac{1}{2}})}{h_{+}} \right)\\ + r_c & = \frac{1}{h_j} \left( \frac{-a(x_{j+\frac{1}{2}})h_j}{2h_{+}} + \frac{d(x_{j+\frac{1}{2}})}{h_{+}} \right) + +We have written the coefficients without dependence on time to simplify the notation, but the coefficients should be calculated at the same time points as their solution variable. However, the coefficients must be linear, they should not depend on the values of the solution variable. + +Discretised equation in matrix form +*********************************** + +Dropping the spatial subscripts and writing in vector form the equations becomes, + +.. math:: + \frac{w^{n+1} - w^{n}}{\tau} & = \theta F(w^{n+1}) + (1-\theta)F(w^{n}) + +where, + +.. math:: + F(w^{n+1}) = M^{n+1} w^{n+1} + s^{n+1} \\ + F(w^{n}) = M^{n}w^{n} + s^{n} + +with the coefficient matrix, + +.. math:: + M = + \begin{align} + \begin{pmatrix} + r_b & r_c & & & 0 \\ + r_a & r_b & r_c & & \\ + & \ddots & \ddots & \ddots& \\ + & & r_a & r_b & r_c \\ + 0 & & & r_a & r_b + \end{pmatrix} + \end{align} + +The :math:`r`-terms have be previously defined as the coefficients that result from the discretisation method. Note that the subscripts for the :math:`r`-terms have been dropped to simplify the notation, but they are functions of space. For example, terms in the first row should be calculated with :math:`j=1`, in the second with :math:`j=2` etc. Solving linear equations is discussed late in section XX. + +.. _label-interpolation-aside: + +Aside: Linear interpolation between cell centre and face values +========================================================================= + +In general, linear interpolation between two points :math:`(x_0, x_1)` can be used to find the value of a function at :math:`f(x)`, + +.. math:: + f(x) = \frac{x - x_1}{x_0 - x_1}f(x_0) + \frac{x - x_0}{x_1 - x_0}f(x_1) + +In a cell centred grid we know the value of the variable :math:`w` at difference points, :math:`w_j` and :math:`w_{j+1}`. We can apply the linear interpolation formulae above to determine value at cell face :math:`w_{j+1/2}`. + +.. math:: + w_{j+1/2} = \frac{x_{j+1/2} - x_{j+1}}{x_{j} - x_{j+1}} w_j + \frac{x_{j+1/2} - x_j}{x_{j+1} - x_j} w_{j+1} + +This can be simplified firstly by using function to represent the distance between cell centres, + +.. math:: + h_{-} = x_j - x_{j-1} \quad h_{+} = x_{j+1} - x_{j} + +to give, + +.. math:: + w_{j+1/2} = \frac{x_{j+1} - x_{j+1/2}}{h_{+}} w_j + \frac{x_{j+1/2} - x_j}{h_{+}} w_{j+1} + +This expression still contains :math:`x_{j+1/2}` which we can simplify further by using an expression for the position of cell centres, + +.. math:: + x_j = \frac{1}{2} \left( x_{j-\frac{1}{2}} + x_{j+\frac{1}{2}} \right) \quad x_{j+1} = \frac{1}{2} \left( x_{j+\frac{1}{2}} + x_{j+\frac{3}{2}} \right) + + +Note, this expression is still valid of non-uniform grids, it simply says that cell centres are always equidistant from two faces. Rearranging the above expression and substituting in for :math:`x_{j}` and :math:`x_{j+1}` terms gives, + +.. math:: + w_{j+1/2} = \frac{\frac{1}{2} \left( x_{j+\frac{1}{2}} + x_{j+\frac{3}{2}} \right) - x_{j+1/2}}{h_{+}} w_j + \frac{x_{j+1/2} - \frac{1}{2} \left( x_{j-\frac{1}{2}} + x_{j+\frac{1}{2}} \right)}{h_{+}} w_{j+1} + + +Finally, by defining the distance between vertices as, :math:`h_j = x_{j+\frac{1}{2}} - x_{j-\frac{1}{2}}`, we can simplify to the following expression, + +.. math:: + w_{j+1/2} = \frac{h_{j+1}}{2h_{+}} w_j + \frac{h_j}{2h_{+}} w_{j+1} + + +Similarly the :math:`w_{j-1/2}` can be found, + +.. math:: + w_{j-1/2} = \frac{h_{j}}{2h_{-}} w_{j-1} + \frac{h_{j-1}}{2h_{-}} w_{j} + diff --git a/_sources/advection_diffusion_boundary_conditions.rst.txt b/_sources/advection_diffusion_boundary_conditions.rst.txt new file mode 100644 index 0000000..32763bc --- /dev/null +++ b/_sources/advection_diffusion_boundary_conditions.rst.txt @@ -0,0 +1,174 @@ +Boundary conditions for the advection-diffusion-reaction equation +----------------------------------------------------------------- + +The advection-diffusion-reaction equation is a particularly good equation to explore apply boundary conditions because it is a more general version of other equations. For example, the diffusion equation, the transport equation and the Poisson equation can all be recovered from this basic form. Moreover, by developing a general scheme for boundary conditions of the advection-reaction-diffusion equation we automatically get a system for imposing boundary conditions on all equations of a similar form. + +Robin boundary conditions (known flux) +************************************** + +Robin specify a known *total flux* comprised of a diffusion and advection component. Note that in the diffusion equation limit (where :math:`a=0`) these boundary conditions reduce to Neumann boundary conditions. + +`Within the finite volume method Robin boundary conditions are naturally resolved `_. This means that there is no need for interpolation or ghost point substitution (although these approaches remain possible) to include the boundary conditions because the flux at the boundary appears naturally in the semi-discretised equation. For example consider the semi-discretised equation evaluated at the boundary cell :math:`\Omega_1`, + +.. figure:: img/boundary_cell_FVM.png + :scale: 100 % + :alt: Finite volume boundary cell at the left hand side. + :align: center + + Finite volume boundary cell at the left hand side. + +.. math:: + w_1^{\prime} = -\frac{\mathcal{F}_{3/2}}{h_1} + \frac{\mathcal{F}_{1/2}}{h_{1}} + \bar{s}_1 + +The Robin boundary condition specifies the flux at :math:`x_{1/2}`, + +.. math:: + \mathcal{F}_{1/2} = g_{R}(x_{1/2}) + +Therefore the boundary condition can be incorporated without invoking any information regarding the ghost cell, + +.. math:: + w_1^{\prime} = \frac{w_1}{h_1}\left( \frac{-a(x_{3/2})h_{2}}{2h_{+}} - \frac{d(x_{3/2})}{h_{+}} \right) + \frac{w_{2}}{h_1} \left( \frac{-a(x_{3/2}) h_1}{2h_{+}} + \frac{d(x_{3/2})}{h_{+}} \right) + \frac{g_{R}(x_L)}{h_1} + \bar{s}_1 + +Similarly applying the same procedure to the :math:`\Omega_J` cell at the right hand side boundary, + +.. figure:: img/boundary_cell_FVM_rhs.png + :scale: 100 % + :alt: Finite volume boundary cell at the right hand side. + :align: center + + Finite volume boundary cell at the right hand side. + +.. math:: + w_J^{\prime} = -\frac{\mathcal{F}_{J+1/2}}{h_J} + \frac{\mathcal{F}_{J-1/2}}{h_J} + \bar{s}_J + +The Robin boundary condition at the right hand side is, + +.. math:: + g_{R}(x_R) = \mathcal{F}_{J+1/2} + +Therefore the naturally resolved boundary condition on the right hand side becomes, + +.. math:: + w_J^{\prime} = \frac{w_{J-1}}{h_J}\left( \frac{a(x_{J-1/2})h_{J}}{2h_{-}} + \frac{d(x_{J-1/2})}{h_{-}} \right) + \frac{w_{J}}{h_J} \left( \frac{a(x_{J-1/2}) h_{J-1}}{2h_{-}} - \frac{d(x_{J-1/2})}{h_{-}} \right) - \frac{g_{R}(x_R)}{h_J} + \bar{s}_J + +Dirichlet boundary conditions (known value) +******************************************* + +.. warning:: + This is only valid provided the initial conditions also satisfy the boundary conditions + +Dirichlet (known value) boundary conditions are trivial to implement. All that is required is to fix the boundary term to be a constant value. If the initial conditions satisfy the boundary conditions (as they should) then all that is needed to to make sure that the time-derivative (the change with time) of the boundary points is always zero. For example, to impose Dirichlet boundary conditions + +.. math:: + w_1^{n+1} = g_D(x_1) \quad w_{J}^{n+1} = g_D(x_J) + +We simply need to specify, + +.. math:: + F_1(w_1^{n}) = 0 \quad F_1(w_1^{n+1}) = 0 + +This can be achieved by multiplying the r.h.s. by the modified identity matrix where the first and last elements are zero, + +.. math:: + w^{\prime} = \text{diag}(0,1,\cdots,1,0) \left( \theta F(w^{n+1}) + (1-\theta)F(w^{n}) \right) + +If the boundary conditions are time dependent, + +.. math:: + w_1^{n+1} = g_D(x_1, t_{n+1}) \quad w_{J}^{n+1} = g_D(x_J, t_{n+1}) + +The we should also add a matrix to the r.h.s. which is contains the changes to the boundary value between the time-steps, + +.. math:: + w^{\prime} = \text{diag}(0,1,\cdots,1,0) \left( \theta F(w^{n+1}) + (1-\theta)F(w^{n}) \right) + \text{diag}(\Delta g_D(x_1), 0, \cdots, 0, \Delta g_D(x_J) ) + +where, + +.. math:: + \Delta g_D(x_1) = g_D(x_1, t_{n+1}) - g_D(x_1, t_{n}) \\ + \Delta g_D(x_J) = g_D(x_J, t_{n+1}) - g_D(x_J, t_{n}) + +The following assumes time-invariant boundary conditions. + +General matrix form with a transient term +***************************************** + +The semi-discretised advection-diffusion-reaction equation can be written in the form below using the :math:`\theta`-method, + +.. math:: + w^{\prime} = \alpha\left( \theta F(w_j^{n+1}) + (1-\theta)F(w_j^{n}) \right) + \beta + +where :math:`F(w)` contains a matrix :math:`M`, a vector of the solution variable :math:`w` and a vector of the reaction term :math:`r`, + +.. math:: + F(w) = Mw + r + +We have introduced a new matrix, :math:`\alpha` and vector, :math:`\beta`. These are used to incorporate the boundary conditions, they have the form, + +.. math:: + \alpha & = \text{diag}\left( \alpha_1, 1, \cdots, 1, \alpha_J \right) \\ + \beta & = \left( \beta_1, 0, \cdots, 0, \beta_J \right) + +The modified coefficient matrix becomes, + +.. math:: + M = + \begin{align} + \begin{pmatrix} + b_1 & c_1 & & & 0 \\ + r_a & r_b & r_c & & \\ + && \ddots & \ddots & \ddots& \\ + && & r_a & r_b & r_c \\ + 0 && & & a_J & b_J + \end{pmatrix} + \end{align} + +Table showing coefficients which should be altered to apply either Robin or Dirichlet boundary conditions. + +.. tabularcolumns:: |m{5cm}|m{5cm}|m{5cm}| + ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| Symbol | Robin | Dirichlet | ++===================+==============================================================================================================+=====================+ +| :math:`b_1` | :math:`\frac{1}{h_1}\left( \frac{-a(x_{3/2})h_{2}}{2h_{+}} - \frac{d(x_{3/2})}{h_{+}} \right)` | :math:`1` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`c_1` | :math:`\frac{1}{h_1} \left( \frac{-a(x_{3/2}) h_1}{2h_{+}} + \frac{d(x_{3/2})}{h_{+}} \right)` | :math:`0` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`a_J` | :math:`\frac{1}{h_J}\left( \frac{a(x_{J-1/2})h_{J}}{2h_{-}} + \frac{d(x_{J-1/2})}{h_{-}} \right)` | :math:`0` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`b_J` | :math:`\frac{1}{h_J} \left( \frac{a(x_{J-1/2}) h_{J-1}}{2h_{-}} - \frac{d(x_{J-1/2})}{h_{-}} \right)` | :math:`1` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`\alpha_1` | :math:`1` | :math:`0` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`\alpha_J` | :math:`1` | :math:`0` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`\beta_1` | :math:`\frac{g_R(x_1)}{h_1}` | :math:`0` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ +| :math:`\beta_J` | :math:`-\frac{g_R(x_J)}{h_J}` | :math:`0` | ++-------------------+--------------------------------------------------------------------------------------------------------------+---------------------+ + +General matrix form without a transient term +******************************************** + +The boundary conditions scheme discussed above is only valid for initial value problems; problems where an initial vector of the solution variable :math:`w^0` is specified along with boundary conditions. PDEs of the advection-diffusion-reaction form that **do not** contain a time derivative are an important class of equations they are called *boundary value problems*, + +.. math:: + 0 = \alpha\left( \theta F(w_j^{n+1}) + (1-\theta)F(w_j^{n}) \right) + \beta + +Boundary value problems do not require initial conditions for the solution variable. For this type of equations we need to use a different general scheme to implement boundary condition values. + +The element of the :math:`M` matrix remain the same, however for Dirichlet boundary conditions the elements must be modified in for the :math:`\alpha` and :math:`\beta` terms, + ++-------------------+----------------------------------------------------------------------------+---------------------------+ +| Symbol | Robin | Dirichlet | ++===================+============================================================================+===========================+ +| :math:`\alpha_1` | :math:`1` | :math:`1` | ++-------------------+----------------------------------------------------------------------------+---------------------------+ +| :math:`\alpha_J` | :math:`1` | :math:`1` | ++-------------------+----------------------------------------------------------------------------+---------------------------+ +| :math:`\beta_1` | :math:`\frac{g_R(x_1)}{h_1}` | :math:`-r(x_1)- g_D(x_1)` | ++-------------------+----------------------------------------------------------------------------+---------------------------+ +| :math:`\beta_J` | :math:`-\frac{g_R(x_J)}{h_J}` | :math:`-r(x_J)- g_D(x_J)` | ++-------------------+----------------------------------------------------------------------------+---------------------------+ + diff --git a/_sources/examples.rst.txt b/_sources/examples.rst.txt new file mode 100644 index 0000000..83edd85 --- /dev/null +++ b/_sources/examples.rst.txt @@ -0,0 +1,38 @@ +Example calculations +-------------------- + +.. note:: + For all simulations :math:`a=1`, :math:`d=0.001` on a domain where :math:`0\leq x \leq 1`, with mesh spacing :math:`h=0.02` and time step :math:`\tau=0.01`. The equations are solver with the boundary conditions :math:`u(0,t)=1` and :math:`u(1,t)=0` and initial conditions :math:`u(x,0)=sin(\pi x)^{100}`. With these boundary conditions a boundary layer will form near :math:`x=1`. + +Uniform grid +************ + +First we test the finite-volume method using a standard uniform grid. Note that the Peclet number for the above parameters is :math:`\mu=20` so the central discretisation scheme is not stable as illustrated by the oscillations in the solution. The upwind scheme does not have a stability criteria related to the Peclet number so the solutions for the *upwind* case are smooth. Finally, the *exponentially fitted* scheme has automatically weighted in favour of the upwind discretisation, the value of :math:`\kappa\approx 0.9`. + +.. raw:: html + +
+ +
+ +Random grid +*********** + +Although a random grid is of no practical use it is a good test of the code because bugs are more likely to show up when symmetry has been reduced. I the follow simulations :math:`\text{min}(\kappa)` =0.004 and :math:`\text{max}(\kappa)` =0.98 so the discretisation scheme is abruptly changing from cell to cell. + +.. raw:: html + +
+ +
+ +Nonuniform grid +*************** + +Nonuniform grids can be used to reduce to *improve* the solution as shown here. The following simulation contains the same number of cells as the previous simulations however the cell centres are clustered towards the right hand boundary. The increased density of cells allow the boundary layer to be resolved. The transient solution computed from the *central* scheme is still significantly affected by the high Peclet number but it is interesting to observe that the steady-state solution of all three methods are very similar. Furthermore, :math:`\kappa\approx0.01-0.1` in the region of the boundary layer which implies the local value of Peclet number as been reduced enough so allow the exponentially fitted scheme to weight in favour of the higher accuracy central discretisation. + +.. raw:: html + +
+ +
diff --git a/_sources/implementation_advection_diffusion.rst.txt b/_sources/implementation_advection_diffusion.rst.txt new file mode 100644 index 0000000..c30b0fc --- /dev/null +++ b/_sources/implementation_advection_diffusion.rst.txt @@ -0,0 +1,199 @@ +Solving the advection-diffusion-reaction equation in Python +----------------------------------------------------------- + +Here we discuss how to implement a solver for the advection-diffusion equation in Python. The notes will consider how to design a solver which minimises code complexity and maximise readability. + +Overview +******** + +At a high-level usage of the code looks like the following, + +.. code-block:: python + :emphasize-lines: 3,6,7,11 + + # Define a mesh + faces = np.linspace(-0.5, 1, 100) + mesh = Mesh(faces) + + # Define coefficients + a = CellVariable(0.01, mesh=mesh) # Advection velocity + d = CellVariable(1e-3, mesh=mesh) # Diffusion coefficient + + # Make a 'model' and apply boundary conditions + k = 1 # Time step + model = Model(faces, a, d, k) + model.set_boundary_conditions(left_value=1., right_value=0.) + + # Ask for the discretised system... + M = model.coefficient_matrix() + alpha = model.alpha_matrix() + beta = model.beta_vector() + + # Solve... + +There are a number of classes (highlighted above) which abstract away details of dealing with finite-volume equations: + + 1. :code:`Mesh` + 2. :code:`CellVariable` + 3. :code:`Model` + +The the :code:`Mesh` and :code:`CellVariable` classes have been inspired by the approach of Fipy_. + +.. _Fipy: http://www.ctcms.nist.gov/fipy/ + +In the following we will highlight the main features of each class and point out how they are useful. The classes do not attempt at doing "*too much*", they simply aid in the creation of the linear system. + +The :code:`Mesh` class +********************** + +:code:`Mesh` objects are initialised with a list of faces locations, which can be non-uniformly distributed if desired. A mesh is completely defined by locations of cell **faces**. Some useful methods, + +.. code:: python + + def h(self, i): + ... + +:code:`h()` returns the cell width for cell with index :code:`i`. This is function is vectorisable by passing an array of the required indices but it *does not* accept "fancy indexing". The reason being, that it is very easy to make mistakes with subscript indexing, the goal here is to make the user be explicit when requesting elements. Note the :code:`self.cell_widths` instance variable returns the numpy array of cell widths is a second way of accessing this data. + +.. code:: python + + def hm(self, i): + ... + +:code:`hm()` returns the distance between cell centroids for the cell at index :code:`i` and :code:`i-1`, that is in the *backwards* or **minus** direction. + +.. code:: python + + def hp(self, i): + ... + +Similarly :code:`hp()` returns the distance between cell centroids for the cell at index :code:`i` and :code:`i+1`, that is in the *forwards* or **plus** direction. + +In addition to the above method, the class contains the instance variables, :code:`self.cells` which returns an array of cell centroid locations, :code:`self.J` which contains the number of cells, and also a copy of the face locations (an array) via :code:`self.faces`. + +The :code:`CellVariable` class +****************************** + +The goal of the :code:`CellVariable` class is to provide a elegant way of automatically interpolating between the cell value and the face value. The class holes values which correspond to the **cell average**. Internally, this class is a subclass of :code:`numpy.ndarray` so it is a fully functioning numpy array. It has a new constructor and additional method which return interpolated values at the cell surfaces. + +A :code:`CellVariable` is initialised with a value for the cell average (this can be a constant or an array-like quantity) and the :code:`Mesh` on which the cell variable is defined. My coupling the cell variable with the mesh the class can perform interpolation between the cell and face values using the methods, + +.. code:: python + + def p(self, i): + ... + + def m(self, i) + ... + +Again :code:`self.p(i)` stands for the *plus* direction and :code:`self.m(i)` stands for the *minus* direction, as such they return values at the right and left face of the cell. The mesh can be returned via the instance variable :code:`cell_variable.mesh`. + + +The :code:`Model` class +*********************** + +The model class is where the creating of the matrices occurs and where boundary conditions can be applied to the problem. For these reasons the class is fairly complicated. + +There are method which return different element of the final matrix. The interior elements are fairly homogenous, the only real difference is where there are spatially varying coefficient of cell widths. For this reason the the method :code:`_interior_matrix_elements()` returns elements which correspond to the lower, central and upper diagonals for a specific index. For example, to calculate the interior matrix elements for mesh point at value :code:`index` one would do the following, + +.. code:: python + + # Return the the interior matrix elements (the r-terms) + # for a particular spatial index + model = Model(...) + index = ... + # The lower, central and, upper diagonal terms of the stencil + ra, rb, rc = model._interior_matrix_elements(index) + +The function names here correspond to the matrix element in the previous section. + +Note that the function is prefixed with an underscore this is because are private, 'users' should have no need to call these method. It is called internally when constructing the finite-volume matrices. However, as this is a overview of how to implement this is an readable and useful way we include this detail. + +The following methods play a similar role, + +.. code:: python + + def _robin_boundary_condition_matrix_elements_left(self): + ... + + def _robin_boundary_condition_matrix_elements_right(self): + ... + + def _dirichlet_boundary_condition_matrix_elements_left(self): + ... + + def _dirichlet_boundary_condition_matrix_elements_right(self): + ... + +They return a list of index-value pairs :code:`([(1,1), a11], [(i,2), b12] ...)`. The functions return the value of element which need to change (with respect to the interior values) in order include boundary conditions. The index-value pair facilitates automatic insertion of the values into the correct matrix element. As we will see later, rather than hard coding the position of the various element if the index and value are specified it makes the destination of the element unambiguous. It also allows the value of the matrix element to be defined at the same point in the code as the location. This is beneficial for providing context and should reduce bugs and complexity. + +The elements for the :math:`\beta` boundary condition vector (which is added to the linear system) are generated from the functions below, + +.. code:: python + + def _robin_boundary_condition_vector_elements_left(self): + ... + + def _robin_boundary_condition_vector_elements_right(self): + ... + + def _dirichlet_boundary_condition_vector_elements_left(self): + ... + + def _dirichlet_boundary_condition_vector_elements_right(self): + ... + +Again, these method should return *index-values* pairs. + +The :code:`Model` class also include some convenience function for checking the value of the Peclet number and the CFL conditions which can be called via, + +.. code:: python + + def peclet_number(self): + return self.a * self.mesh.cell_widths / self.d + + def CFL_condition(self): + return self.a * self.k / self.mesh.cell_widths + + +The method which are intended for the user to actually call when constructing the linear system are, + + +.. code:: python + + def coefficient_matrix(self): + ... + + def alpha_matrix(self): + ... + + def beta_vector(self): + ... + +The linear system for time-stepping can be constructed easily, + +.. code:: python + + # In pseudo-code + model = AdvectionDiffusionModel(...) + M = model.coefficient_matrix() + alpha = model.alpha_matrix() + beta = model.beta_vector() + I = sparse.identity(model.mesh.J) + + # Apply boundary conditions + u_init = ... # + ... + + tau = 0.01 # time step + theta = 0.5 # Implicit/explicit parameter + + u = u_init + for i in range(...): + # time step the linear system, A.x = d + A = I - tau * theta * alpha * M + d = (I + tau * (1-theta) * alpha * M) * u + u = spsolve(A,d) + + +Finally, when initialising a :code:`Model` object the keyword argument :code:`, discretisation` is important. Is can be set to one of the following :code:`'upwind', 'central', 'exponential'`. The :code:`upwind` option uses the classic *first order upwind* discretisation, :code:`central` uses *second-order central* and setting to :code:`exponential` uses an adaptive scheme which will use weight between the central and upwind scheme depending on the local value of the Peclet number. This is the classic 'exponential fitting' or 'Scharfetter-Gummel' discretisation. **N.B.** Scharfetter-Gummel also refers to a method of solving the advection-diffusion equation is a non-coupled manner, this is not the case here where it only refers to the the discretisation method. diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 0000000..335f28b --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,31 @@ +.. FVM Docs documentation master file, created by + sphinx-quickstart on Fri Jun 21 16:17:12 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Notes on implementing the finite-volume method for physical simulations +======================================================================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + overview.rst + advection_diffusion.rst + advection_diffusion_boundary_conditions.rst + solving_linear_equations.rst + solving_nonlinear_equations.rst + poisson.rst + implementation_advection_diffusion.rst + examples.rst + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/_sources/overview.rst.txt b/_sources/overview.rst.txt new file mode 100644 index 0000000..640bec8 --- /dev/null +++ b/_sources/overview.rst.txt @@ -0,0 +1,29 @@ +Overview +-------- + +These are notes that I am making while learning the finite-volume method (FVM). The methods discussed are based on the the book by W. Hundsdorfer and J. G. Verwer, Numerical solutions of time-dependent advection-diffusion reaction equations. + +See the the accompanying project on Github for implementation of the solvers in Python branch, http://danieljfarrell.github.come/FVM/. + + +Finite volumes vs. finite differences +************************************* + +When first starting to solve (partial) differential equation problems a student will first encounter the finite difference method (FDM). The FDM relies on approximating the a differential operator as a series of distinct points, + +.. figure:: img/grid_FDM.png + :scale: 100 % + :alt: Finite difference grid + :align: center + + +In contrast, the FVM uses a fundamental unit of averages over small spatial elements, or cells, + +.. figure:: img/cells_FVM.png + :scale: 100 % + :alt: Finite volume grid. + :align: center + +Although this distinction may seem trivial at first or even over complicated it is a particularly power technique when applied to conservation equations because the conservative properties of the governing equation are maintained even at the discretised level without introducing any approximations. This property is achieved by symbolic integrating the equations over a finite volume, then using solve the problem in terms of the integrated variables. + +This sounds much more complicated that it is, let's look at an example. diff --git a/_sources/poisson.rst.txt b/_sources/poisson.rst.txt new file mode 100644 index 0000000..cd6ce72 --- /dev/null +++ b/_sources/poisson.rst.txt @@ -0,0 +1,101 @@ +The Poisson equation +-------------------- + +The Poisson equation forms the basis of electrostatics and is of the form, + +.. math:: + 0 = (\epsilon(x) \phi_x)_x + \rho(x,t) + +where :math:`\phi` is the electric potential, :math:`\epsilon(x)` is the materials dielectric constant, :math:`\rho(x, t)` is a charge distribution (possibly varying with time), and the :math:`x` subscripts indicate a spatial partial derivative. Note that this equation is reaction-diffusion equation with the diffusion flux :math:`\mathcal{F} = -\epsilon(x) \phi_x`. As with the advection-diffusion equation, this expression holds over the whole domain, therefore it must also hold over a subdomain :math:`\Omega_j=(x_{j-1/2}, x_{j+1/2})`. Integrating over the subdomain, + +.. math:: + 0 = \int_{x_{j-1/2}}^{x_{j+1/2}} (\mathcal{-F})_x~dx + \int_{x_{j-1/2}}^{x_{j+1/2}} \rho(x,t)~dx + +The cell averaged values are therefore, + +.. math:: + 0 = -\frac{\mathcal{F}_{j+1/2}}{h_j} + \frac{\mathcal{F}_{j-1/2}}{h_{j}} + \bar{\rho}_j + +To determine the flux at the cell faces we will take the mean of the fluxes in cells adjacent to the interfaces, + +.. math:: + \mathcal{F}_{j+\frac{1}{2}} = - \epsilon_{j+\frac{1}{2}} \frac{\phi_{j+1}-\phi_j}{h_{+}} + +.. math:: + \mathcal{F}_{j-\frac{1}{2}} = - \epsilon_{j-\frac{1}{2}} \frac{\phi_{j}-\phi_{j-1}}{h_{-}} + +Substituting the definition of the fluxes into the integrated equation and factoring for the electrical potential terms yields, + +.. math:: + 0 = \frac{\phi_{j-1}}{h_j} \left( \frac{\epsilon(x_{j-\frac{1}{2}})}{h_{-}} \right) - \frac{\phi_{j}}{h_j} \left( \frac{\epsilon(x_{j-\frac{1}{2}})}{h_{-}} + \frac{\epsilon(x_{j+\frac{1}{2}})}{h_{+}} \right) + \frac{\phi_{j+1}}{h_j} \left( \frac{\epsilon(x_{j+\frac{1}{2}})}{h_{+}} \right) + \bar{\rho}_j + +Naturally resolved Neumann boundary conditions +********************************************** + +Neumann boundary conditions applied to the finite volume form of the Poisson equation are naturally resolved; that is to say that ghost cell or extrapolation approaches are not needed. Consider the integral form of the Poisson equation at the last cell of the domain on the right hand side, + +.. math:: + 0 = \frac{1}{h_J}\left( \epsilon(x_{R}) \phi_x\bigg|_{x=x_R} - \epsilon(x_{J-1/2}) \phi_x\bigg|_{J-1/2} \right) + \bar{\rho}_J + +The Neumann boundary condition defines the derivative of :math:`\phi` at the the right hand boundary, + +.. math:: + \phi_x\bigg|_{x=x_R} = \sigma_R + +This boundary condition appears naturally as a term in the integral equation. Therefore the boundary condition can be satisfied by substitution into the integral form of the equation at the right hand boundary, + +.. math:: + \frac{1}{h_J}\left( \epsilon(x_{R}) \sigma_R - \epsilon(x_{J-1/2}) \phi_x\bigg|_{J-1/2} \right) + \bar{\rho}_J + +Dirichlet boundary conditions +***************************** + +Fixed value, or Dirichlet, boundary conditions are not naturally resolved and can only be applied to the discretised form of the equation. We need to apply a Dirichlet condition to the left hand side, such that full equation for the cell :math:`j=1` reads, + +.. math:: + \phi(x_L) = \omega_L + +Clearly, this is trivial as all that is required is fix the first row of the matrix equation to be the boundary condition. + +Poisson equation in matrix form +******************************* + +Defining the (vector) coefficients, + +.. math:: + \ell_a(j) & = \frac{1}{h_j}\frac{\epsilon(x_{j-1/2})}{h_{-}} \\ + \ell_b(j) & = -\frac{1}{h_j}\left( \frac{\epsilon_{j-1/2}}{h_{-}} + \frac{\epsilon_{j+1/2}}{h_{+}} \right) \\ + \ell_c(j) & = \frac{1}{h_j}\frac{\epsilon(x_{j+1/2})}{h+} \\ + +With Dirichlet boundary condition on the left hand side and a Neumann boundary condition on the right hand side the Poisson equation can be written in matrix form, + +.. math:: + \begin{align} + \begin{pmatrix} + 1 & 0 & & & 0 \\ + \ell_a(2) & \ell_b(2) & \ell_c(2) & & \\ + & \ddots & \ddots & \ddots & \\ + & & \ell_a(J-1) & \ell_b(J-1) & \ell_c(J-1) \\ + 0 & & & \ell_a(J) & -\ell_a(J) + \end{pmatrix} + \begin{pmatrix} + \phi_1 \\ + \phi_2 \\ + \vdots \\ + \phi_{J-1} \\ + \phi_{J} \\ + \end{pmatrix} + + + \begin{pmatrix} + -\omega_L \\ + \bar{\rho}_2 \\ + \vdots \\ + \bar{\rho}_{J-1} \\ + 1/h_j\epsilon(x_J)\sigma_R + \bar{\rho}_{J} \\ + \end{pmatrix} + = 0 + \end{align} + + + + diff --git a/_sources/solving_linear_equations.rst.txt b/_sources/solving_linear_equations.rst.txt new file mode 100644 index 0000000..a5d7160 --- /dev/null +++ b/_sources/solving_linear_equations.rst.txt @@ -0,0 +1,32 @@ +Solving linear equations +------------------------ + +Provided the equation is linear, meaning that the coefficients, nor the reaction term depend on the solution variable a time-stepping approach can be used to solve the equation. + +Time stepping the advection-diffusion equation +********************************************** + +Here we will rearrange the :math:`\theta`-method form of the advection-diffusion equation, + +.. math:: + \frac{w^{n+1} - w^{n}}{\tau} & = \theta F(w^{n+1}) + (1-\theta)F(w^{n}) + +into the form of a linear system :math:`A\cdot x = d`. Firstly lets move all terms involving future time points the l.h.s, + +.. math:: + w^{n+1} - \theta \tau F(w^{n+1}) = w^{n} + (1-\theta) F(w^{n+1}) + +Replacing the :math:`F` terms with the full matrix expressions and factoring yields, + +.. math:: + \underbrace{(I - \theta\tau M^{n+1})}_{A}\underbrace{w^{n+1}}_{x} = \underbrace{(I + (1-\theta)\tau M^{n})w^{n}}_{d} + +Time-stepping involves solving this equation iteratively. First the initial conditions :math:`w^0` is used to calculate the solution variable at the next point in time :math:`w^1`, then values of the solution variable are updated such that :math:`w^1` is used to calculate :math:`w^2`, and so on and so forth. This procedure is shown in the Figure below, + +.. figure:: img/time_stepping_linear_advection_diffusion.png + :scale: 50 % + :alt: Time stepping the linear advection-diffusion equation. + :align: center + +.. note:: + Parameters used for this simulation. :math:`a=1`, :math:`d=1\times 10^{-3}`, :math:`h=5 \times 10^{-3}`, :math:`\tau=5 \times 10^{-4}` with exponentially fitted discretisation. The initial and boundary conditions where, :math:`u(x,t_{0}) =sin(\pi x)^{100}` with :math:`u(x_1)=1` and :math:`u_x(x_J)=0`. The equation was time stepped for 400 iterations, the plots so the solution at times :math:`t=0,0.05,0.1,0.15,0.2`. \ No newline at end of file diff --git a/_sources/solving_nonlinear_equations.rst.txt b/_sources/solving_nonlinear_equations.rst.txt new file mode 100644 index 0000000..9cac420 --- /dev/null +++ b/_sources/solving_nonlinear_equations.rst.txt @@ -0,0 +1,108 @@ +Solving nonlinear equations +--------------------------- + +.. note:: + Nonlinear equations are defined as those having coefficients which are functions of the solution variable. + +In the previous section we discussed how to solve the linear advection-diffusion-reaction equation with method of time stepping. We used the Crank-Nicolson scheme: the implicit terms provides stability (unrestricted time step) and by also using the explicit term the accuracy of the time integration is improved to second order. However, this approach is only valid for linear equations. + +Nonlinear equations (by definition) cannot be written in linear form, as such the time-stepping approach cannot be used. It remains possible to use a fully explicit method, however in practice this is usually not done because the time step is too restrictive or the explicit form is *unconditionally unstable*. + +Here we discuss three approach which can be used to solve nonlinear equations: + + 1. IMEX (implicit/explicit) time-stepping + 2. Newton iteration + 3. Method of lines + +Fisher's equation +***************** + +We will use Fishers' nonlinear reaction-diffusion equation as an example because it has an analytical solution, + +.. math:: + u_t = d u_{xx} + \beta u(1 - u) + +We can directly apply the discretisation scheme already developed, but we must set :math:`a=0` and note the nonlinear reaction term :math:`\beta u(1 - u)`. With semi-open boundary condition :math:`u(x_0,t)=1` Fishers' equation has the analytical form, + +.. math:: + u(x,t) = \frac{1}{\left(1 + e^{c^{1/2}\left(x - Ut\right)} \right)^2} \\ + +where :math:`U = 5\left(\frac{1}{c}\right)^{1/2}`, and, :math:`c=\frac{6}{d\beta}` + + +IMEX time-stepping +****************** + +The IMEX scheme treats the linear parts of the equation using implicit (or semi-implicit) scheme and the non-linear parts full explicitly. Therefore for Fishers' equation this becomes, + +.. math:: + w^{\prime} = \theta M^{n+1}u^{n+1} + (1-\theta) M^{n}u^{n} + \beta u^n(1-u^n) + +This can be rearranged into the form :math:`A\cdot x = d` and solved by standard time stepping techniques. First we discretised the time derivative :math:`u^{\prime} = \frac{u^{n+1} - u^{n}}{\tau}`, then we rearrange the equation so that future time points are on the l.h.s. and known time points are on the right, + +.. math:: + u^{n+1} - \theta \tau M^{n+1}u^{n+1} = u^{n} + (1-\theta) \tau M^{n}u^{n} + \tau\beta u^n(1-u^n) + +Using the identity matrix, :math:`I` gives the desired result, + +.. math:: + (I - \theta \tau M^{n+1})u^{n+1} = (I + (1-\theta) \tau M^{n} ) u^{n} + \tau \beta u^n(1-u^n) + +This can be readily solved using any linear solver, the results of this approach are shown in the Figure below. Note the poor agreement with the analytical solution for large values of times. IMEX method is known to introduce a global error that grows exponentially with iteration number. The error in the above Figure has been accentuated by deliberately choosing a fairly large time step. A small time step can be used suppress errors. + +.. figure:: img/IMEX_solution_fvm.png + :scale: 50 % + :alt: IMEX solution of Fishers' equation. + :align: center + + Solution of Fishers' equation with an IMEX scheme. + +Newton iteration +**************** + +We have a semi-discretised system of the form :math:`u^{\prime}(t) = F(u(t))` with the application of the :math:`\theta`-method this gives, + +.. math:: + w^{n+1} - w^{n} - (1-\theta) \tau F(w^n) - \theta\tau F(w^{n+1}) = 0 + +with unknown vector :math:`w^{n+1}`. This equation can be solved using Newton iteration. + +.. math:: + \nu^{k+1} = \nu^{k} - (I - \theta\tau A^{n})^{-1} \left( \nu^{k} - u_{n} - (1-\theta) \tau F(w^n) - \theta\tau F(w^{n+1}) \right) + +where :math:`k` is the iteration index (:math:`k\geq0`) and :math:`A^{n}` is the Jacobian matrix of :math:`F(w^n)`. We use the symbol :math:`\nu^{k}` for iteration variables such that they are distinguished from solution of the equation at a real time point :math:`u^n`. The iteration needs a starting value, it is perfectly value to choose :math:`\nu^0 = w^n` or for a better estimate we can precompute one iteration :math:`\nu^0 = w^n + \tau F(w^{n})`. Here we have described the so-called *modified* Newton iteration because the Jacobian is not updated during the iteration, this is known to work well for stiff PDEs. + +Applying the modified Newton iteration to Fisher's equation yields the results shown in the Figure below. The results are much better than for IMEX scheme as the error (particularly for long time points) is much lower. However, the accumulation of global error can still be observed. This is a known feature of so called *single step methods*. In ODE terminology a single step method is one that uses only the most recent past data to evaluate the equation at the future time step. Note even if the equations are implicit (or semi-implicit as with the :math:`\theta`-method) the future state is predicted only from one historic data point. In contrast a *multistep* method would used many previous data points to achieve highly accurate time integration. + + +.. figure:: img/Newton_solution_fvm.png + :scale: 50 % + :alt: IMEX solution of Fishers' equation. + :align: center + + Solution of Fishers' equation with a modified Newton iteration. + +Method of lines +*************** + +.. note:: + For an excellent introduction to the Method of Lines see `the article at scholarpedia.org `_ + +The method of lines is not really a method, it is a way of writing PDEs in such a way that they can be solved by black box *ODE* solvers. The time integration of ODEs is a mature topic. Robust and well tested solvers are readily available on the internet and integrated into many open-source projects for easy use. The method of lines is a way of letting PDE equation take advantage of the mature nature of ODE solvers. + +We have a semi-discretised system of the form, :math:`u^{\prime}(t) = F(u(t))` to use the method of lines we leave the PDE is the semi-discristed form such that is has a continuous time derivative on the l.h.s., i.e. :math:`u^{\prime}(t) = \frac{du(t)}{dt}`. In the semi-discretised form what we really have is no longer a PDE but a system of coupled ODEs. Moreover, a PDE equation is a differential equation which contains differential terms with respect to more that one variable. In this semi-discretised form we have replaced the spatial differential operator with a matrix so there is only one derivative. The equation has become a system of ODEs with number of equation proportional to the number of grid points in our discretisation. + +.. note:: + The discretised boundary conditions are expected to be included in :math:`F(u(t))` such that the problem is well posed. + +There are a wide variety of ODE solvers we are interested in the *implicit* type as we know many PDE problems are unstable in explicit form. The result in the Figure below have been calculated using an implicit multistep Adams-Moulton method (the algorithm used is the highly popular *vode* solver from `netlib.org `_, although we used actually used a Python wrapper from `scipy `_ to perform the calculations shown). This can be thought of as a generalised of the :math:`\theta`-method to include a multiple number of future time steps which are solved simultaneously with a Newtom iteration. Unlike single step method, multistep methods do not show a global error accumulation, so the time integration is a *much* better approximation to analytical solution. Fisher's equation is not particularly stiff so this approach worked well. However for stiff equations a technique called *Backward differentiation formulas* (BDF) is used. The vode solver has the ability to dynamically adapt between stiff and non stiff mode (i.e. Adams-Moulton to BDF) which is one of the reasons for it's popularity. + +The method of lines is a powerful technique for solving semi-discretised PDE problems as much of the details of numerically solving the equation can be off loaded to mature and robust solvers. I do not yet have enough experience with the technique to point out short coming or failings. But it seems that provided the spatial discretisation is stable the details of the time integration can be almost ignored! + +.. figure:: img/MOL_solution_fvm.png + :scale: 50 % + :alt: MOL solution of Fishers' equation. + :align: center + + Solution of Fishers' equation by the method of lines. + diff --git a/_static/basic.css b/_static/basic.css index 43e8baf..f316efc 100644 --- a/_static/basic.css +++ b/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -15,6 +15,12 @@ div.clearer { clear: both; } +div.section::after { + display: block; + content: ''; + clear: left; +} + /* -- relbar ---------------------------------------------------------------- */ div.related { @@ -52,6 +58,8 @@ div.sphinxsidebar { width: 230px; margin-left: -100%; font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; } div.sphinxsidebar ul { @@ -79,16 +87,29 @@ div.sphinxsidebar input { font-size: 1em; } +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; } div.sphinxsidebar #searchbox input[type="submit"] { - width: 30px; + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; } + img { border: 0; + max-width: 100%; } /* -- search page ----------------------------------------------------------- */ @@ -109,7 +130,7 @@ ul.search li a { font-weight: bold; } -ul.search li div.context { +ul.search li p.context { color: #888; margin: 2px 0 0 30px; text-align: left; @@ -123,6 +144,8 @@ ul.keywordmatches li.goodmatch a { table.contentstable { width: 90%; + margin-left: auto; + margin-right: auto; } table.contentstable p.biglink { @@ -150,9 +173,14 @@ table.indextable td { vertical-align: top; } -table.indextable dl, table.indextable dd { +table.indextable ul { margin-top: 0; margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; } table.indextable tr.pcap { @@ -184,19 +212,45 @@ div.genindex-jumpbox { padding: 0.4em; } +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + /* -- general body styles --------------------------------------------------- */ +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + a.headerlink { visibility: hidden; } +a:visited { + color: #551A8B; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, -dt:hover > a.headerlink { +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { visibility: visible; } @@ -208,10 +262,6 @@ div.body td { text-align: left; } -.field-list ul { - padding-left: 1em; -} - .first { margin-top: 0 !important; } @@ -221,19 +271,25 @@ p.rubric { font-weight: bold; } -img.align-left, .figure.align-left, object.align-left { +img.align-left, figure.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } -img.align-right, .figure.align-right, object.align-right { +img.align-right, figure.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } -img.align-center, .figure.align-center, object.align-center { +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { display: block; margin-left: auto; margin-right: auto; @@ -247,30 +303,45 @@ img.align-center, .figure.align-center, object.align-center { text-align: center; } +.align-default { + text-align: center; +} + .align-right { text-align: right; } /* -- sidebars -------------------------------------------------------------- */ -div.sidebar { +div.sidebar, +aside.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; - padding: 7px 7px 0 7px; + padding: 7px; background-color: #ffe; width: 40%; float: right; + clear: right; + overflow-x: auto; } p.sidebar-title { font-weight: bold; } +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + /* -- topics ---------------------------------------------------------------- */ +nav.contents, +aside.topic, div.topic { border: 1px solid #ccc; - padding: 7px 7px 0 7px; + padding: 7px; margin: 10px 0 10px 0; } @@ -292,10 +363,6 @@ div.admonition dt { font-weight: bold; } -div.admonition dl { - margin-bottom: 0; -} - p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; @@ -306,13 +373,55 @@ div.body p.centered { margin-top: 25px; } +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + /* -- tables ---------------------------------------------------------------- */ table.docutils { + margin-top: 10px; + margin-bottom: 10px; border: 0; border-collapse: collapse; } +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-top: 0; @@ -321,14 +430,6 @@ table.docutils td, table.docutils th { border-bottom: 1px solid #aaa; } -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - th { text-align: left; padding-right: 5px; @@ -343,6 +444,126 @@ table.citation td { border-bottom: none; } +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + /* -- other body styles ----------------------------------------------------- */ ol.arabic { @@ -365,11 +586,81 @@ ol.upperroman { list-style: upper-roman; } +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + dl { margin-bottom: 15px; } -dd p { +dd > :first-child { margin-top: 0px; } @@ -383,30 +674,32 @@ dd { margin-left: 30px; } -dt:target, .highlighted { - background-color: #fbe54e; +.sig dd { + margin-top: 0px; + margin-bottom: 0px; } -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; +.sig dl { + margin-top: 0px; + margin-bottom: 0px; } -.field-list ul { - margin: 0; - padding-left: 1em; +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; } -.field-list p { - margin: 0; +dt:target, span.highlighted { + background-color: #fbe54e; } -.refcount { - color: #060; +rect.highlighted { + fill: #fbe54e; } -.optional { - font-size: 1.3em; +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; } .versionmodified { @@ -447,11 +740,26 @@ dl.glossary dt { font-style: oblique; } +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + abbr, acronym { border-bottom: dotted 1px; cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { @@ -459,37 +767,105 @@ pre { overflow-y: hidden; /* fixes display issues on Chrome browsers */ } +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + td.linenos pre { - padding: 5px 0px; border: 0; background-color: transparent; color: #aaa; } table.highlighttable { - margin-left: 0.5em; + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; } table.highlighttable td { - padding: 0 0.5em 0 0.5em; + margin: 0; + padding: 0; } -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; } -tt.descclassname { +div.code-block-caption code { background-color: transparent; } -tt.xref, a tt { +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { background-color: transparent; font-weight: bold; } -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { background-color: transparent; } @@ -521,6 +897,15 @@ span.eqno { float: right; } +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + /* -- printout stylesheet --------------------------------------------------- */ @media print { diff --git a/_static/classic.css b/_static/classic.css new file mode 100644 index 0000000..5530147 --- /dev/null +++ b/_static/classic.css @@ -0,0 +1,269 @@ +/* + * classic.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- classic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +html { + /* CSS hack for macOS's scrollbar (see #1125) */ + background-color: #FFFFFF; +} + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + display: flex; + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #551a8b; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +nav.contents, +aside.topic, +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: unset; + color: unset; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th, dl.field-list > dt { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/_static/default.css b/_static/default.css new file mode 100644 index 0000000..81b9363 --- /dev/null +++ b/_static/default.css @@ -0,0 +1 @@ +@import url("classic.css"); diff --git a/_static/doctools.js b/_static/doctools.js index d4619fd..4d67807 100644 --- a/_static/doctools.js +++ b/_static/doctools.js @@ -2,246 +2,155 @@ * doctools.js * ~~~~~~~~~~~ * - * Sphinx JavaScript utilities for all documentation. + * Base JavaScript utilities for all Sphinx HTML documentation. * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -} - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s == 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * small function to check if an array contains - * a given item. - */ -jQuery.contains = function(arr, item) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] == item) - return true; - } - return false; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node) { - if (node.nodeType == 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); } - return this.each(function() { - highlight(this); - }); }; /** * Small JavaScript module for the documentation. */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); }, /** * i18n support */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", // gettext and ngettext don't access this so that the functions // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } }, - /** - * workaround a firefox stupidity - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; }, - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; }, /** - * init the domain index toggle buttons + * helper function to focus on search bar */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); }, /** - * helper function to hide the search marks again + * Initialise the domain index toggle buttons */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); }, - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - } + }, }; // quick alias for translations -_ = Documentation.gettext; +const _ = Documentation.gettext; -$(document).ready(function() { - Documentation.init(); -}); +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..e21c068 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '0.1', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png index d18082e..a858a41 100644 Binary files a/_static/file.png and b/_static/file.png differ diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 0000000..367b8ed --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png index da1c562..d96755f 100644 Binary files a/_static/minus.png and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png index b3cb374..7107cec 100644 Binary files a/_static/plus.png and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css index d79caa1..0d49244 100644 --- a/_static/pygments.css +++ b/_static/pygments.css @@ -1,15 +1,23 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } -.highlight { background: #eeffcc; } +.highlight { background: #eeffcc; } .highlight .c { color: #408090; font-style: italic } /* Comment */ .highlight .err { border: 1px solid #FF0000 } /* Error */ .highlight .k { color: #007020; font-weight: bold } /* Keyword */ .highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #A00000 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #FF0000 } /* Generic.Error */ .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #00A000 } /* Generic.Inserted */ @@ -40,12 +48,15 @@ .highlight .nv { color: #bb60d5 } /* Name.Variable */ .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ .highlight .mf { color: #208050 } /* Literal.Number.Float */ .highlight .mh { color: #208050 } /* Literal.Number.Hex */ .highlight .mi { color: #208050 } /* Literal.Number.Integer */ .highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ .highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ @@ -56,7 +67,9 @@ .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ .highlight .ss { color: #517918 } /* Literal.String.Symbol */ .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js index 663be4c..92da3f8 100644 --- a/_static/searchtools.js +++ b/_static/searchtools.js @@ -1,560 +1,619 @@ /* - * searchtools.js_t + * searchtools.js * ~~~~~~~~~~~~~~~~ * - * Sphinx JavaScript utilties for the full-text search. + * Sphinx JavaScript utilities for the full-text search. * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ +"use strict"; /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words, hlwords is the list of normal, unstemmed - * words. the first one is used to find the occurance, the - * latter for highlighting it. + * Simple result scoring code. */ - -jQuery.makeSearchSummary = function(text, keywords, hlwords) { - var textLower = text.toLowerCase(); - var start = 0; - $.each(keywords, function() { - var i = textLower.indexOf(this.toLowerCase()); - if (i > -1) - start = i; - }); - start = Math.max(start - 120, 0); - var excerpt = ((start > 0) ? '...' : '') + - $.trim(text.substr(start, 240)) + - ((start + 240 - text.length) ? '...' : ''); - var rv = $('
').text(excerpt); - $.each(hlwords, function() { - rv = rv.highlightText(this, 'highlighted'); - }); - return rv; +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; } +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; /** - * Porter Stemmer + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlinks", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; -/** - * Search Module - */ -var Search = { - - _index : null, - _queued_query : null, - _pulse_status : -1, - - init : function() { - var params = $.getQueryParameters(); - if (params.q) { - var query = params.q[0]; - $('input[name="q"]')[0].value = query; - this.performSearch(query); - } + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; }, - loadIndex : function(url) { - $.ajax({type: "GET", url: url, data: null, success: null, - dataType: "script", cache: true}); + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); }, - setIndex : function(index) { - var q; - this._index = index; - if ((q = this._queued_query) !== null) { - this._queued_query = null; - Search.query(q); + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); } }, - hasIndex : function() { - return this._index !== null; - }, + hasIndex: () => Search._index !== null, - deferQuery : function(query) { - this._queued_query = query; - }, + deferQuery: (query) => (Search._queued_query = query), - stopPulse : function() { - this._pulse_status = 0; - }, + stopPulse: () => (Search._pulse_status = -1), - startPulse : function() { - if (this._pulse_status >= 0) - return; - function pulse() { + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { Search._pulse_status = (Search._pulse_status + 1) % 4; - var dotString = ''; - for (var i = 0; i < Search._pulse_status; i++) - dotString += '.'; - Search.dots.text(dotString); - if (Search._pulse_status > -1) - window.setTimeout(pulse, 500); + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); }; pulse(); }, /** - * perform a search for something + * perform a search for something (or wait until index is loaded) */ - performSearch : function(query) { + performSearch: (query) => { // create the required interface elements - this.out = $('#search-results'); - this.title = $('

' + _('Searching') + '

').appendTo(this.out); - this.dots = $('').appendTo(this.title); - this.status = $('

').appendTo(this.out); - this.output = $('