diff --git a/README.md b/README.md index 9180d92c..9ba8f702 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ **KernelInterpolation.jl** is a [Julia](https://julialang.org/) package that implements methods for multivariate interpolation in arbitrary dimension based on symmetric (conditionally) positive-definite kernels -with a focus on radial-basis functions. It can be used for classical interpolation of scattered data, as well as for generalized +with a focus on radial basis functions. It can be used for classical interpolation of scattered data, as well as for generalized (Hermite-Birkhoff) interpolation by using a meshfree collocation approach. This can be used to solve partial differential equations both stationary ones and time-dependent ones by using some time integration method from [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl). diff --git a/docs/src/index.md b/docs/src/index.md index e9a074f6..6e66d305 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -11,7 +11,7 @@ [**KernelInterpolation.jl**](https://github.com/JoshuaLampert/KernelInterpolation.jl) is a [Julia](https://julialang.org/) package that implements methods for multivariate interpolation in arbitrary dimension based on symmetric (conditionally) positive-definite kernels -with a focus on radial-basis functions. It can be used for classical interpolation of scattered data, as well as for generalized +with a focus on radial basis functions. It can be used for classical interpolation of scattered data, as well as for generalized (Hermite-Birkhoff) interpolation by using a meshfree collocation approach. This can be used to solve partial differential equations both stationary ones and time-dependent ones by using some time integration method from [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl). diff --git a/docs/src/interpolation.md b/docs/src/interpolation.md index c91ad612..39870b7c 100644 --- a/docs/src/interpolation.md +++ b/docs/src/interpolation.md @@ -233,8 +233,9 @@ to visualize the interpolant are the `Delaunay2D` filter to create a surface plo ## Overview of kernels and adding a custom kernel -In the previous example, we used the [`ThinPlateSplineKernel`](@ref), which is a predefined kernel in KernelInterpolation.jl. There are a number of different -kernels already defined, which can be used in an analogous way. For an overview of the existing radial-symmetric kernels, see the following table. +In the previous example, we used the [`ThinPlateSplineKernel`](@ref), which is a predefined kernel in KernelInterpolation.jl. +There is a number of different [kernels already defined](@ref api-kernels), which can be used in an analogous way. For an +overview of the existing radial-symmetric kernels, see the following table. | Kernel name | Formula | Order | Smoothness | --- | --- | --- | --- diff --git a/docs/src/pdes.md b/docs/src/pdes.md index f1c1d942..69a3f083 100644 --- a/docs/src/pdes.md +++ b/docs/src/pdes.md @@ -2,12 +2,15 @@ Kernel methods are also suitable to solve partial differential equations (PDEs), which is also sometimes known as Hermite-Birkhoff interpolation, a special case of generalized interpolation. In an abstract setting generalized interpolation deals with the following -problem: Given a Hilbert space $H$ and a set of functionals $\{\lambda_i\}_{i = 1}^N\subset H^*$ ($H^*$ being the dual space), find a function $s\in H$ such that -$\lambda_i(s) = f_i$ for $i = 1,\ldots,N$ for given function values $f_i$. [Classical interpolation](@ref classical_interpolation), discussed in the previous section, -corresponds to the case where $H$ is a reproducing kernel Hilbert space (RKHS) and $\lambda_i(s) = s(x_i)$ are point evaluations at the nodes $x_i$ for -$X = \{x_i\}_{i = 1}^N$. In the case of Hermite-Birkhoff interpolation, the functionals $\lambda_i$ are not point evaluations but differential operators, which +problem: Given a Hilbert space $H$ and a set of functionals $\{\lambda_i\}_{i = 1}^N\subset H^*$ ($H^*$ being the dual space), find a +function $s\in H$ such that $\lambda_i(s) = f_i$ for $i = 1,\ldots,N$ for given function values $f_i$. +[Classical interpolation](@ref classical_interpolation), discussed in the previous section, corresponds to the case where $H$ is a +reproducing kernel Hilbert space (RKHS) and $\lambda_i(s) = s(x_i)$ are point evaluations at the nodes $x_i$ for $X = \{x_i\}_{i = 1}^N$. +In the case of Hermite-Birkhoff interpolation, the functionals $\lambda_i$ are not point evaluations but differential operators, which are applied to the function $s$ and then evaluated at the nodes $x_i$. +The following is mostly based on the book by Fasshauer [^Fasshauer2015]. + ## Stationary PDEs Consider the following general stationary PDE in a domain $\Omega\subset\mathbb{R}^d$: @@ -16,34 +19,35 @@ Consider the following general stationary PDE in a domain $\Omega\subset\mathbb{ \mathcal{L}u = f, ``` -where $\mathcal{L}$ is a linear differential operator of order $m$, $u$ is the unknown function and $f$ is a given function. The operator $\mathcal{L}$ can be -written as +where $\mathcal{L}$ is a linear differential operator of order $m$, $u$ is the unknown function and $f$ is a given function. +The operator $\mathcal{L}$ can be written as ```math \mathcal{L}u = \sum_{|\alpha|\leq m} a_\alpha D^\alpha u, ``` -where $D^\alpha = \partial_{x_1}^{\alpha_1}\cdots\partial_{x_d}^{\alpha_d}$ is a partial derivative of order $|\alpha| = \alpha_1 + \cdots + \alpha_d$. -Note that in the context of PDEs we usually use the notation $u$ for the unknown function instead of $s$ as in the general interpolation problem. -For a complete description of the PDE, we also need to specify boundary conditions on the boundary $\partial\Omega$, which can be written with a -boundary operator $\mathcal{B}$ as +where $D^\alpha = \partial_{x_1}^{\alpha_1}\cdots\partial_{x_d}^{\alpha_d}$ is a partial derivative of order +$|\alpha| = \alpha_1 + \cdots + \alpha_d$. Note that in the context of PDEs we usually use the notation $u$ for the +unknown function instead of $s$ as in the general interpolation problem. For a complete description of the PDE, we also +need to specify boundary conditions on the boundary $\partial\Omega$, which can be written with a boundary operator $\mathcal{B}$ as ```math \mathcal{B}u = g, ``` -where $g$ is a given function. As boundary operator $\mathcal{B}$ we usually consider the identity operator, which corresponds to Dirichlet boundary conditions. -Like in the case of classical interpolation, we pick a set of nodes $X_I = \{x_i\}_{i = 1}^{N_I}\subset\Omega$. Due to the additional boundary conditions, we also -pick a set of nodes $X_B = \{x_i\}_{i = N_I + 1}^N\subset\partial\Omega$. Let $N = N_I + N_B$ and $X = X_I\cup X_B$. We again formulate an ansatz function $u$ -as a linear combination of basis functions. In the simplest case, we use the same linear combination (neglecting polynomial augmentation for simplicity), i.e. +where $g$ is a given function. As boundary operator $\mathcal{B}$ we usually consider the identity operator, which corresponds to +Dirichlet boundary conditions. Like in the case of classical interpolation, we pick a set of nodes $X_I = \{x_i\}_{i = 1}^{N_I}\subset\Omega$. +Due to the additional boundary conditions, we also pick a set of nodes $X_B = \{x_i\}_{i = N_I + 1}^N\subset\partial\Omega$. +Let $N = N_I + N_B$ and $X = X_I\cup X_B$. We again formulate an ansatz function $u$ as a linear combination of basis functions. +In the simplest case, we use the same linear combination (neglecting polynomial augmentation for simplicity), i.e. ```math u(x) = \sum_{j = 1}^N c_jK(x, x_j), ``` -where $K$ is the kernel function. This approach is also non as non-symmetric collocation or Kansa's method. By enforcing the conditions $\mathcal{L}u(x_i) = f(x_i)$ -for $i = 1,\ldots,N_I$ and $\mathcal{B}u(x_i) = g(x_i)$ for $i = N_I + 1,\ldots,N$ we obtain a linear system of equations for the coefficients $c_i$, which can be -written as +where $K$ is the kernel function. This approach is also non as non-symmetric collocation or Kansa's method. By enforcing the +conditions $\mathcal{L}u(x_i) = f(x_i)$ for $i = 1,\ldots,N_I$ and $\mathcal{B}u(x_i) = g(x_i)$ for $i = N_I + 1,\ldots,N$ we +obtain a linear system of equations for the coefficients $c_i$, which can be written as ```math \begin{pmatrix} @@ -54,8 +58,8 @@ f_{X_I} \\ g_{X_B} \end{pmatrix}, ``` -where $\tilde{A}_L\in\mathbb{R}^{N_I\times N}$ and $\tilde{A}_B\in\mathbb{R}^{N_I\times N}$ are the matrices corresponding to the conditions at the interior and boundary nodes, -respectively, i.e. +where $\tilde{A}_L\in\mathbb{R}^{N_I\times N}$ and $\tilde{A}_B\in\mathbb{R}^{N_I\times N}$ are the matrices corresponding to +the conditions at the interior and boundary nodes, respectively, i.e. ```math \begin{align*} @@ -68,16 +72,20 @@ Since the kernel function is known and differentiable, we can compute the deriva !!! note In KernelInterpolation.jl, the derivatives of the kernel function are computed using automatic differentiation (AD) by using - [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl). This allows for flexibility, simplicity, and easier extension, but it might be slower than computing - the derivatives analytically. If you are interested in a more efficient implementation, you can have a look at the test set "Differential operators" in the - [test suite of KernelInterpolation.jl](https://github.com/JoshuaLampert/KernelInterpolation.jl/blob/main/test/test_unit.jl). This test set not only shows how to use - analytical derivatives, but also how to define your own differential operators, which can be used to define custom PDEs. + [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl). This allows for flexibility, simplicity, and easier extension, + but it might be slower than computing the derivatives analytically. If you are interested in a more efficient implementation, + you can have a look at the test set "Differential operators" in the + [test suite of KernelInterpolation.jl](https://github.com/JoshuaLampert/KernelInterpolation.jl/blob/main/test/test_unit.jl). + This test set not only shows how to use analytical derivatives, but also how to define your own differential operators, + which can be used to define custom PDEs. -Note, however, that the system matrix $A = \begin{pmatrix} \tilde{A}_L \\ \tilde{A}_B \end{pmatrix}$ is not invertible in general because it not symmetric anymore as it -was the case in the classical interpolation. Thus, this approach is also called non-symmetric collocation. +Note, however, that the system matrix $A = \begin{pmatrix} \tilde{A}_L \\ \tilde{A}_B \end{pmatrix}$ is not invertible in general +because it not symmetric anymore as it was the case in the classical interpolation. Thus, this approach is also called +non-symmetric collocation. -Let us see how this can be implemented in KernelInterpolation.jl by solving the Poisson equation ``-\Delta u = f`` in an L-shaped domain. We start by defining the equation -(thus the differential operator) and the right-hand side. KernelInterpolation.jl already provides a set of predefined differential operators and equations. +Let us see how this can be implemented in KernelInterpolation.jl by solving the Poisson equation ``-\Delta u = f`` in an L-shaped +domain. We start by defining the equation (thus the differential operator) and the right-hand side. KernelInterpolation.jl already +provides a set of predefined [differential operators](@ref api-diffops) and [equations](@ref api-equations). ```@example poisson using KernelInterpolation @@ -90,8 +98,8 @@ pde = PoissonEquation(f) u(x, equations) = sinpi(x[1]) * cospi(x[2] / 2) ``` -Next, we define the domain and the boundary of the L-shaped domain. We use a homogeneous grid for the nodes and filter the inner and boundary nodes in two separate -[`NodeSet`](@ref)s. +Next, we define the domain and the boundary of the L-shaped domain. We use a homogeneous grid for the nodes and filter the inner +and boundary nodes in two separate [`NodeSet`](@ref)s. ```@example poisson function create_L_shape(N) @@ -120,8 +128,8 @@ end nodeset_inner, nodeset_boundary = create_L_shape(6) ``` -Finally, we define the boundary condition, the kernel, and collect all necessary information in a [`SpatialDiscretization`](@ref), which can be solved by calling the -[`solve_stationary`](@ref) function. +Finally, we define the boundary condition, the kernel, and collect all necessary information in a [`SpatialDiscretization`](@ref), +which can be solved by calling the [`solve_stationary`](@ref) function. ```@example poisson # Dirichlet boundary condition (here taken from analytical solution) @@ -132,8 +140,8 @@ sd = SpatialDiscretization(pde, nodeset_inner, g, nodeset_boundary, kernel) itp = solve_stationary(sd) ``` -The result `itp` is an [`Interpolation`](@ref) object, which can be used to evaluate the solution at arbitrary points. We can save the solution on a finer grid -to a VTK file and visualize it. +The result `itp` is an [`Interpolation`](@ref) object, which can be used to evaluate the solution at arbitrary points. We can +save the solution on a finer grid to a VTK file and visualize it. ```@example poisson many_nodes_inner, many_nodes_boundary = create_L_shape(20) @@ -145,30 +153,33 @@ vtk_save(joinpath(OUT, "poisson_2d_L_shape"), many_nodes, itp, x -> u(x, pde); rm(joinpath(OUT, "poisson_2d_L_shape.vtu")) #clean up again # hide ``` -The resulting VTK file can be visualized with a tool like ParaView. After applying the filter `Warp by Scalar`, setting the coloring accordingly, and changing the -"Representation" to "Point Gaussian", we obtain the following visualization: +The resulting VTK file can be visualized with a tool like ParaView. After applying the filter `Warp by Scalar`, setting +the coloring accordingly, and changing the "Representation" to "Point Gaussian", we obtain the following visualization: ![Poisson equation in an L shape domain](poisson_L_shape.png) ## Time-dependent PDEs -KernelInterpolation.jl also supports the solution of time-dependent PDEs. The idea is to use the same approach as above for the spatial part of the PDE and then obtain -an ordinary differential equation (ODE), which can be solved by some time integration method (method of lines). The general form of a time-dependent PDE is +KernelInterpolation.jl also supports the solution of time-dependent PDEs. The idea is to use the same approach as above for +the spatial part of the PDE and then obtain an ordinary differential equation (ODE), which can be solved by some time +integration method (method of lines). The general form of a time-dependent PDE is ```math \partial_t u + \mathcal{L}u = f, ``` -where $\mathcal{L}$ is a linear differential operator of order $m$ and $f$ is a given function. The initial condition is given by $u(x, 0) = u_0(x)$. Boundary conditions -are applied as before. Similar to the stationary case, we discretize the spatial part of the PDE by collocation and obtain a system of ODEs for the coefficients $c_j$ of -the basis functions, i.e. now the coefficients depend on time: +where $\mathcal{L}$ is a linear differential operator of order $m$ and $f$ is a given function. The initial condition is +given by $u(x, 0) = u_0(x)$. Boundary conditions are applied as before. Similar to the stationary case, we discretize the +spatial part of the PDE by collocation and obtain a system of ODEs for the coefficients $c_j$ of the basis functions, i.e. +now the coefficients depend on time: ```math u(t, x) = \sum_{j = 1}^N c_j(t)K(x, x_j), ``` -where $c_j(t)$ are the coefficients at time $t$. We again divide the spatial domain in a set of points in the inner domain $X_I$ and at the boundary $X_B$. Plugging in -the ansatz function into the PDE and the boundary conditions and evaluating at the nodes, we obtain the following system of ODEs and algebraic equations: +where $c_j(t)$ are the coefficients at time $t$. We again divide the spatial domain in a set of points in the inner domain +$X_I$ and at the boundary $X_B$. Plugging in the ansatz function into the PDE and the boundary conditions and evaluating at +the nodes, we obtain the following system of ODEs and algebraic equations: ```math \begin{align*} @@ -183,8 +194,9 @@ These equations can be written compactly as a differential-algebraic equation (D Mc^\prime(t) = -Ac(t) + b ``` -where $c(t) = [c_1(t), \ldots, c_N(t)]^T$ is the vector of coefficients, $M\in\mathbb{R}^{N\times N}$ is a (singular) mass matrix, $A\in\mathbb{R}^{N\times N}$ is the system matrix, -and $b = \begin{pmatrix}f_{X_I}\\ g_{X_B}\end{pmatrix}\in\mathbb{R}^N$. The matrices are given by +where $c(t) = [c_1(t), \ldots, c_N(t)]^T$ is the vector of coefficients, $M\in\mathbb{R}^{N\times N}$ is a (singular) mass +matrix, $A\in\mathbb{R}^{N\times N}$ is the system matrix, and $b = \begin{pmatrix}f_{X_I}\\ g_{X_B}\end{pmatrix}\in\mathbb{R}^N$. +The matrices are given by ```math M = \begin{pmatrix} \tilde{A_I}\\0\end{pmatrix} \quad\text{and}\quad A = \begin{pmatrix} \tilde{A_L}\\\tilde{A_B}\end{pmatrix}, @@ -209,16 +221,30 @@ The coefficients for the initial conditions can be computed from the initial con c_0 = (u_0)_X. ``` -For the solution of the DAE system, KernelInterpolation.jl uses the library [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), which already provides a -wide range of time integration methods. Note that this is a differential-algebraic equation (DAE) system, which is more difficult to solve than a simple ODE system. Thus, -we are restricted to specialized time integration methods, which can handle DAEs. We recommend using the `Rodas5P` method, which is a Rosenbrock method for stiff DAEs. See +For the solution of the DAE system, KernelInterpolation.jl uses the library [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), +which already provides a wide range of time integration methods. Note that this is a differential-algebraic equation (DAE) +system, which is more difficult to solve than a simple ODE system. Thus, we are restricted to specialized time integration +methods, which can handle DAEs. We recommend using the `Rodas5P` method, which is a Rosenbrock method for stiff DAEs. See also the [documentation of OrdinaryDiffEq.jl](https://docs.sciml.ai/DiffEqDocs/latest/tutorials/dae_example/) for more information. -In KernelInterpolation.jl, you can use the constructor of a [`Semidiscretization`](@ref) in a very similar way as [`SpatialDiscretization`](@ref), but with the additional -initial condition. This can be turned into an `ODEProblem` object from the OrdinaryDiffEq.jl ecosystem by calling [`semidiscretize`](@ref). The resulting `ODEProblem` -can then be solved by calling the [`solve`](https://docs.sciml.ai/DiffEqDocs/latest/basics/common_solver_opts/) function from OrdinaryDiffEq.jl. The resulting object is an -`ODESolution` object, which can be transferred to a [`TemporalInterpolation`](@ref) by calling `TemporalInterpolation` on it. This object acts similarly to an [`Interpolation`](@ref), -but has an additional time argument, e.g., `itp = titp(1.0)` gives an interpolation object `itp` from a temporal interpolation `titp` at time `t = 1.0`. -For a complete example of a time-dependent PDE, see, e.g., [an example of the heat equation](https://github.com/JoshuaLampert/KernelInterpolation.jl/blob/main/examples/PDEs/heat_2d_basic.jl). -KernelInterpolation.jl provides some callbacks that are can be passed to `solve` in order to call them during the time integration process. These can be used to monitor the solution -or to save it to a file. To date, these are [`AliveCallback`](@ref), [`SaveSolutionCallback`](@ref), and [`SummaryCallback`](@ref). +In KernelInterpolation.jl, you can use the constructor of a [`Semidiscretization`](@ref) in a very similar way as +[`SpatialDiscretization`](@ref), but with the additional initial condition. This can be turned into an `ODEProblem` object +from the OrdinaryDiffEq.jl ecosystem by calling [`semidiscretize`](@ref). The resulting `ODEProblem` can then be solved by calling +the [`solve`](https://docs.sciml.ai/DiffEqDocs/latest/basics/common_solver_opts/) function from OrdinaryDiffEq.jl. The resulting object +is an `ODESolution` object, which can be transferred to a [`TemporalInterpolation`](@ref) by calling `TemporalInterpolation` on it. +This object acts similarly to an [`Interpolation`](@ref), but has an additional time argument, e.g., `itp = titp(1.0)` gives an +interpolation object `itp` from a temporal interpolation `titp` at time `t = 1.0`. + +For a complete example of a time-dependent PDE, see, e.g., +[an example of the heat equation](https://github.com/JoshuaLampert/KernelInterpolation.jl/blob/main/examples/PDEs/heat_2d_basic.jl). +KernelInterpolation.jl provides [some callbacks](@ref api-callbacks) that are can be passed to `solve` in order to call them during +the time integration process. These can be used to monitor the solution or to save it to a file. To date, these are [`AliveCallback`](@ref), +[`SaveSolutionCallback`](@ref), and [`SummaryCallback`](@ref). + +## References + +[^Fasshauer2015]: + Fasshauer (2015): + Kernel-based Approximation Methods using MATLAB, + World Scientific, + [DOI: 10.1142/9335](https://doi.org/10.1142/9335). diff --git a/docs/src/ref.md b/docs/src/ref.md index da9dc5a6..350614e7 100644 --- a/docs/src/ref.md +++ b/docs/src/ref.md @@ -6,4 +6,75 @@ CurrentModule = KernelInterpolation ```@autodocs Modules = [KernelInterpolation] +Pages = ["KernelInterpolation.jl"] +``` + +## [Kernel functions](@id api-kernels) + +```@autodocs +Modules = [KernelInterpolation] +Pages = [joinpath("kernels", "kernels.jl"), joinpath("kernels", "radialsymmetric_kernel.jl"), joinpath("kernels", "special_kernel.jl")] +``` + +## Node sets + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["nodes.jl"] +``` + +## Interpolation + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["interpolation.jl"] +``` + +## [Differential Operators](@id api-diffops) + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["differential_operators.jl"] +``` + +## [Partial differential equations](@id api-equations) + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["equations.jl"] +``` + +## Discretization + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["discretization.jl"] +``` + +## Kernel matrices + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["kernel_matrices.jl"] +``` + +## [Callbacks](@id api-callbacks) + +```@autodocs +Modules = [KernelInterpolation] +Pages = [joinpath("callbacks_step", "alive.jl"), joinpath("callbacks_step", "summary.jl"), joinpath("callbacks_step", "save_solution.jl")] +``` + +## Input/Output + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["io.jl"] +``` + +## Utilities + +```@autodocs +Modules = [KernelInterpolation] +Pages = ["util.jl"] ``` diff --git a/docs/src/tutorial_differentiating_interpolation.md b/docs/src/tutorial_differentiating_interpolation.md index f21de644..02193eea 100644 --- a/docs/src/tutorial_differentiating_interpolation.md +++ b/docs/src/tutorial_differentiating_interpolation.md @@ -112,8 +112,9 @@ derivative in the ``i``-th direction, ``i\in\{1,\ldots,d\}``, of the interpolati differentiation (AD) by using [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl). This allows for flexibility, simplicity, and easier extension, but it might be slower than computing the derivatives analytically. -KernelInterpolation.jl already provides some common differential operators. For example, we can compute the first derivative -of the interpolation `itp` at a specific point `x` by using the [`PartialDerivative`](@ref) operator. +KernelInterpolation.jl already provides some [common differential operators](@ref api-diffops). For example, +we can compute the first derivative of the interpolation `itp` at a specific point `x` by using the +[`PartialDerivative`](@ref) operator. ```@example diff-itp d1 = PartialDerivative(1) diff --git a/src/KernelInterpolation.jl b/src/KernelInterpolation.jl index 2fff5d61..69813509 100644 --- a/src/KernelInterpolation.jl +++ b/src/KernelInterpolation.jl @@ -1,3 +1,14 @@ +""" + KernelInterpolation + +**KernelInterpolation.jl** is a Julia package that implements methods for multivariate interpolation in arbitrary +dimension based on symmetric (conditionally) positive-definite kernels with a focus on radial basis functions. +It can be used for classical interpolation of scattered data, as well as for generalized (Hermite-Birkhoff) +interpolation by using a meshfree collocation approach. This can be used to solve partial differential equations +both stationary ones and time-dependent ones by using some time integration method from OrdinaryDiffEq.jl. + +See also: [KernelInterpolation.jl](https://github.com/JoshuaLampert/KernelInterpolation.jl) +""" module KernelInterpolation using DiffEqCallbacks: PeriodicCallback, PeriodicCallbackAffect