11 lines
10 KiB
JSON
11 lines
10 KiB
JSON
{
|
||
"hash": "8ddbe1576b9082865bcdb5a69365209d",
|
||
"result": {
|
||
"markdown": "# The problem-algorithm-solve interface\n\n\n\nThis section uses these add-on packages:\n\n``` {.julia .cell-code}\nusing Plots\nusing MonteCarloMeasurements\n```\n\n\n\n\n---\n\n\nThe [DifferentialEquations.jl](https://github.com/SciML) package is an entry point to a suite of `Julia` packages for numerically solving differential equations in `Julia` and other languages. A common interface is implemented that flexibly adjusts to the many different problems and algorithms covered by this suite of packages. In this section, we review a very informative [post](https://discourse.julialang.org/t/function-depending-on-the-global-variable-inside-module/64322/10) by discourse user `@genkuroki` which very nicely demonstrates the usefulness of the problem-algorithm-solve approach used with `DifferentialEquations.jl`. We slightly modify the presentation below for our needs, but suggest a perusal of the original post.\n\n\n##### Example: FreeFall\n\n\nThe motion of an object under a uniform gravitational field is of interest.\n\n\nThe parameters that govern the equation of motions are the gravitational constant, `g`; the initial height, `y0`; and the initial velocity, `v0`. The time span for which a solution is sought is `tspan`.\n\n\nA problem consists of these parameters. Typical `Julia` usage would be to create a structure to hold the parameters, which may be done as follows:\n\n::: {.cell execution_count=4}\n``` {.julia .cell-code}\nstruct Problem{G, Y0, V0, TS}\n g::G\n y0::Y0\n v0::V0\n tspan::TS\nend\n\nProblem(;g=9.80665, y0=0.0, v0=30.0, tspan=(0.0,8.0)) = Problem(g, y0, v0, tspan)\n```\n\n::: {.cell-output .cell-output-display execution_count=5}\n```\nProblem\n```\n:::\n:::\n\n\nThe above creates a type, `Problem`, *and* a default constructor with default values. (The original uses a more sophisticated setup that allows the two things above to be combined.)\n\n\nJust calling `Problem()` will create a problem suitable for the earth, passing different values for `g` would be possible for other planets.\n\n\nTo solve differential equations there are many different possible algorithms. Here is the construction of two types to indicate two algorithms:\n\n::: {.cell execution_count=5}\n``` {.julia .cell-code}\nstruct EulerMethod{T}\n dt::T\nend\nEulerMethod(; dt=0.1) = EulerMethod(dt)\n\nstruct ExactFormula{T}\n dt::T\nend\nExactFormula(; dt=0.1) = ExactFormula(dt)\n```\n\n::: {.cell-output .cell-output-display execution_count=6}\n```\nExactFormula\n```\n:::\n:::\n\n\nThe above just specifies a type for dispatch –- the directions indicating what code to use to solve the problem. As seen, each specifies a size for a time step with default of `0.1`.\n\n\nA type for solutions is useful for different `show` methods or other methods. One can be created through:\n\n``` {.julia .cell-code}\nstruct Solution{Y, V, T, P<:Problem, A}\n y::Y\n v::V\n t::T\n prob::P\n alg::A\nend\n```\n\n\nThe different algorithms then can be implemented as part of a generic `solve` function. Following the post we have:\n\n::: {.cell execution_count=7}\n``` {.julia .cell-code}\nsolve(prob::Problem) = solve(prob, default_algorithm(prob))\ndefault_algorithm(prob::Problem) = EulerMethod()\n\nfunction solve(prob::Problem, alg::ExactFormula)\n g, y0, v0, tspan = prob.g, prob.y0, prob.v0, prob.tspan\n dt = alg.dt\n t0, t1 = tspan\n t = range(t0, t1 + dt/2; step = dt)\n\n y(t) = y0 + v0*(t - t0) - g*(t - t0)^2/2\n v(t) = v0 - g*(t - t0)\n\n Solution(y.(t), v.(t), t, prob, alg)\nend\n\nfunction solve(prob::Problem, alg::EulerMethod)\n g, y0, v0, tspan = prob.g, prob.y0, prob.v0, prob.tspan\n dt = alg.dt\n t0, t1 = tspan\n t = range(t0, t1 + dt/2; step = dt)\n\n n = length(t)\n y = Vector{typeof(y0)}(undef, n)\n v = Vector{typeof(v0)}(undef, n)\n y[1] = y0\n v[1] = v0\n\n for i in 1:n-1\n v[i+1] = v[i] - g*dt # F*h step of Euler\n y[i+1] = y[i] + v[i]*dt # F*h step of Euler\n end\n\n Solution(y, v, t, prob, alg)\nend\n```\n\n::: {.cell-output .cell-output-display execution_count=8}\n```\nsolve (generic function with 3 methods)\n```\n:::\n:::\n\n\nThe post has a more elegant means to unpack the parameters from the structures, but for each of the above, the parameters are unpacked, and then the corresponding algorithm employed. As of version `v1.7` of `Julia`, the syntax `(;g,y0,v0,tspan) = prob` could also be employed.\n\n\nThe exact formulas, `y(t) = y0 + v0*(t - t0) - g*(t - t0)^2/2` and `v(t) = v0 - g*(t - t0)`, follow from well-known physics formulas. Each answer is wrapped in a `Solution` type so that the answers found can be easily extracted in a uniform manner.\n\n\nFor example, plots of each can be obtained through:\n\n::: {.cell execution_count=8}\n``` {.julia .cell-code}\nearth = Problem()\nsol_euler = solve(earth)\nsol_exact = solve(earth, ExactFormula())\n\nplot(sol_euler.t, sol_euler.y;\n label=\"Euler's method (dt = $(sol_euler.alg.dt))\", ls=:auto)\nplot!(sol_exact.t, sol_exact.y; label=\"exact solution\", ls=:auto)\ntitle!(\"On the Earth\"; xlabel=\"t\", legend=:bottomleft)\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n{}\n:::\n:::\n\n\nFollowing the post, since the time step `dt = 0.1` is not small enough, the error of the Euler method is rather large. Next we change the algorithm parameter, `dt`, to be smaller:\n\n::: {.cell execution_count=9}\n``` {.julia .cell-code}\nearth₂ = Problem()\nsol_euler₂ = solve(earth₂, EulerMethod(dt = 0.01))\nsol_exact₂ = solve(earth₂, ExactFormula())\n\nplot(sol_euler₂.t, sol_euler₂.y;\n label=\"Euler's method (dt = $(sol_euler₂.alg.dt))\", ls=:auto)\nplot!(sol_exact₂.t, sol_exact₂.y; label=\"exact solution\", ls=:auto)\ntitle!(\"On the Earth\"; xlabel=\"t\", legend=:bottomleft)\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n{}\n:::\n:::\n\n\nIt is worth noting that only the first line is modified, and only the method requires modification.\n\n\nWere the moon to be considered, the gravitational constant would need adjustment. This parameter is part of the problem, not the solution algorithm.\n\n\nSuch adjustments are made by passing different values to the `Problem` constructor:\n\n::: {.cell execution_count=10}\n``` {.julia .cell-code}\nmoon = Problem(g = 1.62, tspan = (0.0, 40.0))\nsol_eulerₘ = solve(moon)\nsol_exactₘ = solve(moon, ExactFormula(dt = sol_euler.alg.dt))\n\nplot(sol_eulerₘ.t, sol_eulerₘ.y;\n label=\"Euler's method (dt = $(sol_eulerₘ.alg.dt))\", ls=:auto)\nplot!(sol_exactₘ.t, sol_exactₘ.y; label=\"exact solution\", ls=:auto)\ntitle!(\"On the Moon\"; xlabel=\"t\", legend=:bottomleft)\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n{}\n:::\n:::\n\n\nThe code above also adjusts the time span in addition to the graviational constant. The algorithm for exact formula is set to use the `dt` value used in the `euler` formula, for easier comparison. Otherwise, outside of the labels, the patterns are the same. Only those things that need changing are changed, the rest comes from defaults.\n\n\nThe above shows the benefits of using a common interface. Next, the post illustrates how *other* authors could extend this code, simply by adding a *new* `solve` method. For example,\n\n::: {.cell execution_count=11}\n``` {.julia .cell-code}\nstruct Symplectic2ndOrder{T}\n dt::T\nend\nSymplectic2ndOrder(;dt=0.1) = Symplectic2ndOrder(dt)\n\nfunction solve(prob::Problem, alg::Symplectic2ndOrder)\n g, y0, v0, tspan = prob.g, prob.y0, prob.v0, prob.tspan\n dt = alg.dt\n t0, t1 = tspan\n t = range(t0, t1 + dt/2; step = dt)\n\n n = length(t)\n y = Vector{typeof(y0)}(undef, n)\n v = Vector{typeof(v0)}(undef, n)\n y[1] = y0\n v[1] = v0\n\n for i in 1:n-1\n ytmp = y[i] + v[i]*dt/2\n v[i+1] = v[i] - g*dt\n y[i+1] = ytmp + v[i+1]*dt/2\n end\n\n Solution(y, v, t, prob, alg)\nend\n```\n\n::: {.cell-output .cell-output-display execution_count=12}\n```\nsolve (generic function with 4 methods)\n```\n:::\n:::\n\n\nHad the two prior methods been in a package, the other user could still extend the interface, as above, with just a slight standard modification.\n\n\nThe same approach works for this new type:\n\n::: {.cell execution_count=12}\n``` {.julia .cell-code}\nearth₃ = Problem()\nsol_sympl₃ = solve(earth₃, Symplectic2ndOrder(dt = 2.0))\nsol_exact₃ = solve(earth₃, ExactFormula())\n\nplot(sol_sympl₃.t, sol_sympl₃.y; label=\"2nd order symplectic (dt = $(sol_sympl₃.alg.dt))\", ls=:auto)\nplot!(sol_exact₃.t, sol_exact₃.y; label=\"exact solution\", ls=:auto)\ntitle!(\"On the Earth\"; xlabel=\"t\", legend=:bottomleft)\n```\n\n::: {.cell-output .cell-output-display execution_count=13}\n{}\n:::\n:::\n\n\nFinally, the author of the post shows how the interface can compose with other packages in the `Julia` package ecosystem. This example uses the external package `MonteCarloMeasurements` which plots the behavior of the system for perturbations of the initial value:\n\n::: {.cell execution_count=13}\n``` {.julia .cell-code}\nearth₄ = Problem(y0 = 0.0 ± 0.0, v0 = 30.0 ± 1.0)\nsol_euler₄ = solve(earth₄)\nsol_sympl₄ = solve(earth₄, Symplectic2ndOrder(dt = 2.0))\nsol_exact₄ = solve(earth₄, ExactFormula())\n\nylim = (-100, 60)\nP = plot(sol_euler₄.t, sol_euler₄.y;\n label=\"Euler's method (dt = $(sol_euler₄.alg.dt))\", ls=:auto)\ntitle!(\"On the Earth\"; xlabel=\"t\", legend=:bottomleft, ylim)\n\nQ = plot(sol_sympl₄.t, sol_sympl₄.y;\n label=\"2nd order symplectic (dt = $(sol_sympl₄.alg.dt))\", ls=:auto)\ntitle!(\"On the Earth\"; xlabel=\"t\", legend=:bottomleft, ylim)\n\nR = plot(sol_exact₄.t, sol_exact₄.y; label=\"exact solution\", ls=:auto)\ntitle!(\"On the Earth\"; xlabel=\"t\", legend=:bottomleft, ylim)\n\nplot(P, Q, R; size=(720, 600))\n```\n\n::: {.cell-output .cell-output-display execution_count=14}\n{}\n:::\n:::\n\n\nThe only change was in the problem, `Problem(y0 = 0.0 ± 0.0, v0 = 30.0 ± 1.0)`, where a different number type is used which accounts for uncertainty. The rest follows the same pattern.\n\n\nThis example, shows the flexibility of the problem-algorithm-solver pattern while maintaining a consistent pattern for execution.\n\n",
|
||
"supporting": [
|
||
"solve_files"
|
||
],
|
||
"filters": [],
|
||
"includes": {}
|
||
}
|
||
} |