Examen final¶
Ce notebook est à soumettre sur Educnet avant 11h30.
L’examen comporte trois exercices indépendants. Dans chaque exercice, les cellules peuvent éventuellement dependre des cellules précèdentes.
Afin de faciliter l'évaluation de votre code, ne pas changer les signatures des fonctions à implémenter.
La cellule ci-dessous importe les bibliothèques utilisées dans ce notebook. Ce sont a priori des bibliothèques déjà présentes dans l'installation de base de Julia ou déjà installées en TD. Si une ou plusieurs d'entre elles manquent sur votre machine, vous êtes invités à les installer au préalable dans le gestionnaire de bibliothèques d'une console.
using ForwardDiff
using LaTeXStrings
using LinearAlgebra
using Plots
using Polynomials
using Random
Plots.default(titlefontsize=12,
xlabelfontsize=10,
ylabelfontsize=10,
legendfontsize=10,
xtickfontsize=10,
ytickfontsize=10)
[Exercice 1] Intégration par interpolation¶
Le but de cet exercice est de calculer l'intégrale de la fonction $f \colon [-5, \,5] \to \mathbb{R} $ donnée par
$$ f (x) = \log(1 + |x| - x^3 + x^4). $$
Pour cela, nous vous proposons d'interpoler cette fonction par un polynome sur l'intervalle $[-5, \, 5]$, puis d'intégrer ce dernier.
- Ecrire une fonction
interpolate(func, d, a)qui retourne le polynôme $p$ (de typePolynomialde la bibliothèquePolynomials.jl) résultant de l'interpolation polyonmiale de la fonctionfuncsur l'intervalle $[-a, a]$ de degré $d$. Pour cela, vous pourrez utiliser la fonctionfitde la bibliothèquePolynomials.jl, et choisir les nœuds d'interpolation de la manière qui vous semble la mieux adaptée.
f(x) = log(1 + abs(x) - x^3 + x^4)
function interpolate(func, d, a)
### BEGIN SOLUTION
chebyshev_nodes(k, n) = cospi((2k - 1) / (2n))
x = a * chebyshev_nodes.(1:d+1, d+1)
y = func.(x)
p = fit(x, y)
### END SOLUTION
return p
end;
@assert typeof(interpolate(f, 20, 5)) <: Polynomial
@assert length(interpolate(f, 20, 5).coeffs) == 21
@assert begin xs = LinRange(-1, 1, 200); interpolate(cos, 20, 1).(xs) .- cos.(xs) |> maximum end ≤ 1e-14
@assert begin xs = LinRange(-1, 1, 200); func = x -> x^3; p = interpolate(func, 20, 1); sum(abs2, p.(xs) - func.(xs)) end ≤ 1e-14
- Illustrer sur un même graphique la fonction
fdéfinie ci-dessus, ainsi que son interpolation de degré 20.
# ### BEGIN SOLUTION
x = LinRange(-5, 5, 201)
p = interpolate(f, 20, 5)
plot(x, f, label=L"Function $f$")
plot!(x, p.(x), label="Interpolation")
# ### END SOLUTION
Écrire une fonction
integrate_via_interp(func, d, a)qui retourne une approximation de l'intégrale $$ \int_{-a}^a \mathrm{func}(x) \, \mathrm{d} x\, , $$ en calculant d'abord une interpolation de degrédde la fonction passée en argument, puis en intégrant cette dernière de manière exacte. Pour cela, on pourra utiliser la fonctionintegratede la bibliothèquePolynomials.jl, qui permet de calculer la primitve d'un polynôme:P = integrate(p)
function integrate_via_interp(func, d, a)
### BEGIN SOLUTION
p = interpolate(func, d, a)
P = integrate(p)
return P(a) - P(-a)
### END SOLUTION
end;
funcs = [x -> 1, x -> x, x -> x^2, x -> x^3]
@assert integrate_via_interp.(funcs, 20, 1) ≈ [2, 0, 2/3, 0]
@assert integrate_via_interp(exp, 20, 1) ≈ exp(1) - exp(-1)
@assert integrate_via_interp(cos, 20, 5) ≈ sin(5) - sin(-5)
@assert integrate_via_interp(x -> 1 + 3x^3, 5, 1) ≈ 2.0
@assert abs(integrate_via_interp(f, 20, 5) - 34.070458) ≤ 0.5
[Exercice 2] Une méthode d'ordre élevé pour les équations non-linéaires¶
Le but de cet exercice est de mettre en œuvre une méthode de résolution pour l'équation non-linéaire scalaire suivante : $$ f(x) = 0, \qquad f \colon \mathbb R \to \mathbb R $$ Inspiré par la méthode de Newton-Raphson, qui est basée sur une approximation affine de $f$ à chaque itération, on a l'idée de définir un schéma itératif basé sur des approximations quadratiques de $f$. Plus précisément, on considère la méthode itérative qui consiste à définir, étant donné une approximation $x_k$, l'approximation $x_{k+1}$ de la manière suivante :
Considérons le polynôme de Taylor quadratique $p_k$ approximant $f$ autour de $x_k$: $$ p_k(x) = \frac{f''(x_k)}{2} (x - x_k)^2 + f'(x_k) (x - x_k) + f(x_k). $$
Si ce polynôme admet deux racines $r_1 \leqslant r_2$ (éventuellement égales), alors on pose $x_{k+1} = r_1$ si $|x_k - r_1| \leqslant |x_k - r_2|$, et $x_{k+1} = r_2$ sinon.
Si ce polynôme n'admet pas de racine, on considérera que la méthode ne fonctionne pas. La fonction à implémenter ci-dessous devra retourner
nothing.
- Implémenter la méthode suggérée
et retourner l'ensemble des itérations,
y compris le point initial
xpassé en argument et l'approximation finale, dans un vecteurxs. On supposera que l'argumentxest un nombre flottant (Float16,Float32,Float64ouBigFloat), et on demande de retourner dans le vecteurxsdes flottants du même type. Icifest une fonction,dfest sa dérivée, etddfsa dérivée seconde.
function super_newton(x, f, df, ddf; maxiter=100, ε = 1e-12)
xs = [x]
### BEGIN SOLUTION
for n in 1:maxiter
abs(f(x)) < ε && return xs
a, b, c = ddf(x), df(x), f(x)
Δ = b^2 - 2*a*c
Δ < 0 && return nothing
x1, x2 = x + (-b - √Δ) / a, x + (-b + √Δ) / a
x = abs(x - x1) < abs(x - x2) ? x1 : x2
push!(xs, x)
end
### END SOLUTION
return xs
end;
funs = x -> exp(x) - 2, exp, exp
@assert super_newton(1., funs...) |> typeof === Vector{Float64}
@assert super_newton(1.f0, funs...) |> typeof === Vector{Float32}
@assert super_newton(BigFloat(1.), funs...) |> typeof === Vector{BigFloat}
@assert super_newton(1., funs...)[end] ≈ log(2)
@assert super_newton(3., x -> x^3 - x - 6, x -> 3*x^2 - 1, x -> 6x)[end] ≈ 2
@assert super_newton(1., x -> sin(x) - .5, cos, x -> -sin(x))[end] ≈ π/6
@assert super_newton(1., x -> x^3, x -> 3x^2, x -> 6x) === nothing
- Faire un graphique illustrant l'évolution de l'erreur dans le cas où $f(x) = x^3 - 2$.
Initialiser l'itération à $x_0 = 1$,
et utiliser le format
BigFloatavec une grande précision, ainsi qu'une très petite tolérance $ε = 10^{-200}$, afin d'illustrer la convergence aussi clairement que possible. Pour éviter d'encombrer le namespace global, on encapsulera le code du graphique dans une fonction.
# Augmentation de la précision pour le format BigFloat
setprecision(1000, base=10)
# Fonction dont on cherche une racine
f(x) = x^3 - 2.0
function my_plot_error()
p = plot(title=L"Evolution of the error for $f(x) = x^3 - 2$")
### BEGIN SOLUTION
x = BigFloat(1)
x∞ = cbrt(BigFloat(2.0))
df(x) = 3x^2
ddf(x) = 6x
xs = super_newton(x, f, df, ddf; ε = 1e-200)
yplot = abs.(xs .- x∞)
plot!(yplot, yscale=:log10, label=nothing)
scatter!(yplot, yscale=:log10, label=nothing)
### END SOLUTION
return p
end
my_plot_error()
- Supposons qu'une suite $(x_0, x_1, \dotsc)$,
dont on ne connaît qu'un nombre fini d'éléments $(x_0, \dotsc, x_K)$,
converge à l'ordre $q$ vers une limite $x_{*} \in \mathbb R$,
c'est à dire que
$$
\lim_{k \to \infty}
\frac{|x_{k+1} - x_{\infty}|}{|x_{k} - x_{\infty}|^q} \in \mathbb R.
$$
Écrire une fonction
estimate_order(xs, x∞)permettant d'estimer l'ordre de convergence $q$.
function estimate_order(xs, x∞)
### BEGIN SOLUTION
logerr = (log∘abs).(xs .- x∞)
p = fit(logerr[1:end-1], logerr[2:end], 1)
return p[1]
### END SOLUTION
end;
@assert estimate_order([1e-1, 1e-2, 1e-4, 1e-8, 1e-16], 0.) ≈ 2
@assert estimate_order([1e-1, 1e-3, 1e-9, 1e-27], 0.) ≈ 3
@assert estimate_order([1e-1, 1e-2, 1e-3, 1e-4], 0.) ≈ 1
# Estimation de l'ordre de convergence de la méthode `super_newton`
function get_order_super_newton()
x∞ = cbrt(BigFloat(2.0))
xs = super_newton(BigFloat(2.), x -> x^3 - 2, x -> 3x^2, x -> 6x; ε = 1e-200)
q = round(Float64(estimate_order(xs, x∞)), sigdigits=5)
end
println("Order of the proposed method: ", get_order_super_newton())
### BEGIN HIDDEN TESTS
@assert round.(Int, get_order_super_newton()) == 3
### END HIDDEN TESTS
Order of the proposed method: 3.0022
- Pour éviter de calculer des dérivées manuellement,
on souhaite utiliser la différentiation automatique.
Écrire une fonction
super_newton_autoimplémentant la méthode tout en exploitant la différentiation automatique. Il peut être utile pour ce faire d'utiliser la fonctionForwardDiff.derivative, et de réutiliser la méthodesuper_newtondéfinie ci-dessus.
function super_newton_auto(x, f; maxiter=100, ε=1e-12)
### BEGIN SOLUTION
df(x) = ForwardDiff.derivative(f, x)
ddf(x) = ForwardDiff.derivative(df, x)
return super_newton(x, f, df, ddf; maxiter=maxiter, ε=ε)
### END SOLUTION
end;
@assert super_newton_auto(1., x -> exp(x) - 2) |> typeof === Vector{Float64}
@assert super_newton_auto(1., x -> exp(x) - 2)[end] ≈ log(2)
@assert super_newton_auto(1., x -> sin(x) - .5)[end] ≈ π/6
@assert super_newton_auto(3., x -> x^3 - x - 6)[end] ≈ 2
[Exercice 3] Équation de Lotka-Volterra¶
Dans le cadre d’une étude écologique sur une île isolée, on aimerait étudier l’interaction entre deux populations animales :
- 🐇 des lapins sauvages, qui se reproduisent rapidement en l’absence de prédateurs,
- 🦊 des renards, qui se nourrissent exclusivement de lapins.
Leur dynamique est modélisée par les équations classiques de Lotka-Volterra :
$$ \left\{ \begin{aligned} X'(s) &= \alpha \, X(s) - \beta \, X(s)\,Y(s), \\ Y'(s) &= - \gamma \, Y(s) + \delta \, X(s)\,Y(s), \end{aligned} \right. $$
où :
- $s$ représente le temps.
- $X(s)$ représente la population de lapins à l’instant $ s $,
- $Y(s)$ représente la population de renards à l’instant $ s $,
- $\alpha$ est le taux de reproduction naturelle des lapins en l’absence de renards,
- $\beta$ est la contribution de chaque renard au taux de mortalité des lapins,
- $\delta$ est la contribution de chaque lapin au taux de reproduction des renards,
- $\gamma$ est le taux de mortalité naturelle des renards en l’absence de nourriture.
Ce modèle permet d’analyser les cycles prédateur-proie typiques dans les écosystèmes fermés. Il est simple de montrer que l'unique état d'équilibre non trivial est donné par $(X_{\rm eq}, Y_{\rm eq}) = (\gamma/\delta, \alpha/\beta)$. Afin d'éliminer certains paramètres, on a l'idée d'introduire les variables adimensionnées $X/X_{\rm eq}$ et $Y/Y_{\rm eq}$. Pour simplifier encore davantage, on prendra comme nouvelle unité temporelle la constante de temps associée à l'extinction exponentielle de renards en l'absence de lapins, ce qui revient à poser $x(t) = X(t/γ)/X_{\rm eq}$ et $y(t) = Y(t/γ)/Y_{\rm eq}$. L'évolution du couple $(x, y)$ est alors donnée par $$ \left\{ \begin{aligned} x'(t) &= r \, x(t) - r \, x(t)\,y(t), \\ y'(t) &= - y(t) + x(t)\,y(t), \end{aligned} \right. \qquad r := \frac{\alpha}{\gamma} \tag{Lotka-Volterra} $$ On supposera dans la première partie de l'exercice que tous les paramètres α,β,γ,δ sont scalaires avec de plus $α = γ$, de sorte que $r = 1$. Dans la seconde partie, on adoptera un modèle plus réaliste dans lequel $α$ dépend de $x$.
- En posant $\mathbf Z := (x, y)^\top$,
on peut réécrire l'équation de (Lotka-Volterra) sous la forme générique suivante :
$$
\mathbf Z'(t) = \mathbf f\bigl(t, \mathbf Z(s)\bigr).
\tag{Generic-ODE}
$$
Écrire la fonction $\mathbf f$ sous forme d'une fonction Julia
f(t, Z).
function f(t, Z)
### BEGIN SOLUTION
x, y = Z
return [x - x*y, -y + x*y]
### END SOLUTION
end;
@assert f(1, [0, 1]) ≈ [0, -1]
@assert f(1, [1, 0]) ≈ [1, 0]
@assert f(1, [5, 2]) ≈ [5 - 10, -2 + 10]
- Écrire une fonction
heun3(g, tₙ, Zₙ, Δ)implémentant un pas de temps de taille $\Delta$ de la méthode de Heun d'ordre 3 pour une équation différentielle générique de la forme $\mathbf Z' = \mathbf g(t, \mathbf Z)$. Cette méthode est basée sur l'itération suivante: $$ \mathbf Z_{n+1} = \mathbf Z_n + \frac{\Delta}{4}\left(\mathbf k_1 + 3 \mathbf k_3 \right), $$ où \begin{align*} \mathbf k_1 &= \ \mathbf g(t_n, \mathbf Z_n), \\ \mathbf k_2 &= \ \mathbf g\!\left(t_n + \frac{\Delta}{3}, \mathbf Z_n + \frac{\Delta}{3} \mathbf k_1\right), \\ \mathbf k_3 &= \ \mathbf g\!\left(t_n + \frac{2\Delta}{3}, \mathbf Z_n + \frac{2\Delta}{3} \mathbf k_2\right), \\ \end{align*} La fonction devra renvoyer $\mathbf Z_{n+1}$.
function heun3(g, tₙ, Zₙ, Δ)
### BEGIN SOLUTION
k₁ = g(tₙ, Zₙ )
k₂ = g(tₙ + Δ/3, Zₙ + Δ/3 * k₁)
k₃ = g(tₙ + 2Δ/3, Zₙ + 2Δ/3 * k₂)
return Zₙ + Δ/4 * (k₁ + 3k₃)
### END SOLUTION
end;
@assert heun3((t, Z) -> [1.],0., [0.], 1.) ≈ [1.0]
@assert heun3((t, Z) -> [t], 1., [0.], 1.) ≈ [3/2]
@assert heun3((t, Z) -> [1.; t; t^2], 0., [2.; 2.; 2.], 1.) ≈ [3., 5/2, 7/3]
- Écrire une fonction
solve_ode(g, Z₀, Δ, niter)pour résoudre (Generic-ODE) en effectuantniteritérations de la méthode de Heun d'ordre 3 avec un pas de temps fixeΔ. Votre fonction devra renvoyer un vecteur de tempsts(comprenant le temps initial, donc de tailleniter + 1), ainsi qu'un vecteur de vecteurs contenant la solution à ces temps.
function solve_ode(g, Z₀, Δ, niter)
### BEGIN SOLUTION
ts = (0:niter) * Δ
Z, Zs = Z₀, [Z₀]
for t in ts[1:end-1]
Z = heun3(g, t, Z, Δ)
push!(Zs, Z)
end
return ts, Zs
### END SOLUTION
end;
@assert solve_ode(f, [1.0, 0.0], 0.01, 100) |> length == 2
@assert solve_ode(f, [1.0, 0.0], 0.01, 100)[1] |> length == 101
@assert solve_ode(f, [1.0, 0.0], 0.01, 100)[1][101] ≈ 1
@assert solve_ode(f, [1.0, 0.0], 0.01, 100)[1] ≈ 0:.01:1
@assert solve_ode(f, [1.0, 0.0], 0.01, 100)[2] |> x -> sum(z -> z[2], x) == 0.
@assert solve_ode(f, [0.0, 1.0], 0.1, 1000)[2][end][2] ≤ 1e-3
- Écrire une fonction
my_plot_evolution(g, Z₀, Δ, niter)permettant d'illustrer sur un même graphique l'évolution des populations de lapins et de renards en fonction du temps, pour une condition initialeZ₀donnée. Cette fonction prendra explicitement en argument une functiong, car on l'utilisera plus tard avec un autre modèle d'évolution des populations. La cellule en dessous vous permettra de tester votre code.
function my_plot_evolution(g, Z₀, Δ, niter)
x₀, y₀ = Z₀
p = plot(title="(x₀, y₀) = ($x₀, $y₀)")
### BEGIN SOLUTION
ts, Zs = solve_ode(g, Z₀, Δ, niter)
xs, ys = first.(Zs), last.(Zs)
plot!(ts, xs, label="Lapins")
plot!(ts, ys, label="Renards")
### END SOLUTION
return p
end;
Δ, niter1, niter2 = .01, 1000, 3000
p1 = my_plot_evolution(f, [1.0, 0.0], Δ, niter1)
p2 = my_plot_evolution(f, [0.0, 1.0], Δ, niter1)
p3 = my_plot_evolution(f, [1.0, 1.0], Δ, niter1)
p4 = my_plot_evolution(f, [3.0, 1.0], Δ, niter2)
p5 = my_plot_evolution(f, [5.0, 2.0], Δ, niter2)
p6 = my_plot_evolution(f, [0.5, 0.5], Δ, niter2)
plot(p1, p2, p3, p4, p5, p6; layout=(2, 3), size=(900, 600))
- Dans la cellule suivante (qui ne doit en aucun cas être détruite), commenter les figures obtenues au point précédent pour les conditions initiales suivantes
- $(0.1, 0.0)$
- $(0.0, 1.0)$
- $(1.0, 1.0)$
- $(3.0, 1.0)$
- Écrire une fonction
my_plot_phase(g, all_Z₀, Δ, niter)permettant d'illustrer, sur un même graphique, l'évolution des populations de lapins et de renards dans le plan $xy$ (lapins en abscisse et renards en ordonnée) pour toutes les conditions initiales contenues dans le vecteurall_Z₀, supposé de typeVector{Vector{Float64}}. La cellule suivante vous permettra de tester votre code.
function my_plot_phase(g, all_Z₀, Δ, niter)
p = plot(title="Evolution in the phase space")
### BEGIN SOLUTION
for Z₀ in all_Z₀
x₀, y₀ = Z₀
_, Zs = solve_ode(g, Z₀, Δ, niter)
xs, ys = getindex.(Zs, 1), getindex.(Zs, 2)
plot!(xs, ys, label="(x₀, y₀) = ($x₀, $y₀)")
end
### END SOLUTION
return p
end;
all_Z₀ = [[3.0, 1.0],
[5.0, 2.0],
[0.5, 0.5]]
my_plot_phase(f, all_Z₀, .01, 3000)
- Suite à un problème technique, les données sur les conditions initiales de l’étude écologique ont été perdues.
Les populations animales ont néanmoins été observées au temps $t=1$,
où elles étaient données par $(x₁, y₁) = (2, 2)$.
En se basant sur ce point d'observation et sur le modèle d'évolution de (Lotka-Volterra),
estimer les populations au temps $t = 0$.
Il peut être utile pour cela d'utiliser la bibliothèque
ForwardDiff.jl, en particulier la fonctionForwardDiff.jacobian.
function recover_initial_data()
Z₁ = [2., 2.]
### BEGIN SOLUTION
obj(Z) = solve_ode(f, Z, .01, 100)[2][end] - Z₁
Z₀ = [5., .1]
for n in 1:10
Z₀ -= ForwardDiff.jacobian(obj, Z₀)\obj(Z₀)
end
### END SOLUTION
return Z₀
end
Z₀ = recover_initial_data()
@show Z₀
my_plot_evolution(f, Z₀, .01, 100)
Z₀ = [2.2095710044614583, 0.4962424586387766]
@assert length(Z₀) == 2
### BEGIN HIDDEN TESTS
@assert round.(Z₀, digits=3) ≈ [2.21, 0.496]
### END HIDDEN TESTS
- L'évolution de Lotka-Volterra avec $r = 1$ conserve la quantité $V(x, y) = x - \ln(x) + y - \ln(y)$ (pour mémoire le logarithme népérien est donné par
logen Julia). Écrire une fonctioncalculate_energy_mismatchretournant, pour la fonctionsolve_odeavec le pas de temps donné en argument, la déviation maximale de l'énergie sur l'intervalle de temps $[0, 1]$. Plus précisément, la fonction devra retourner $$ E(Δ) := \max_{n \in \mathbb N, nΔ \in [0, 1]} \Bigl| V\bigl( x_n, y_n \bigr) - V\bigl( x(0), y(0) \bigr) \Bigr| $$
V(z) = z[1] - log(z[1]) + z[2] - log(z[2])
function calculate_energy_mismatch(Z₀, Δ)
### BEGIN SOLUTION
niter = floor(Int, 1/Δ)
ts, Zs = solve_ode(f, Z₀, Δ, niter)
E = maximum(abs, V.(Zs) .- V(Z₀))
### END SOLUTION
return E
end;
Z₀ = [2.0, 2.0]
@assert calculate_energy_mismatch(Z₀, .1) > 1e-8
@assert calculate_energy_mismatch(Z₀, .1) < 1e-3
@assert calculate_energy_mismatch(Z₀, .01) > 1e-12
@assert calculate_energy_mismatch(Z₀, .01) < 1e-6
@assert calculate_energy_mismatch(Z₀, .001) < 1e8
- Il est possible de montrer que $E(Δ) \propto Δ^q$, de manière approximative. Estimer l'exposant $q$. Pour cela, approximer par une droite la fonction $\log(Δ) \mapsto \log\bigl(E(Δ)\bigr)$, et déduire la valeur de $q$ du coefficient directeur de cette droite.
function estimate_q()
### BEGIN SOLUTION
Z₀ = [2.0, 2.0]
Δs = 1 ./ 2 .^(4:10)
Es = [calculate_energy_mismatch(Z₀, Δ) for Δ in Δs]
q = fit(log.(Δs), log.(Es), 1)[1]
### END SOLUTION
return q
end;
println("Order of convergence: ", estimate_q())
Order of convergence: 3.023790725383018
@assert estimate_q() > 0
### BEGIN HIDDEN TESTS
@assert round.(Int, estimate_q()) == 3
### END HIDDEN TESTS
- On considère à partir de maintenant un modèle plus réaliste, incluant une saturation du taux de reproduction des lapins lorsque leur population devient trop importante. Concrètement, on considère que le taux de reproduction des lapins est désormais donné par $\widehat α(x) = α (1-\frac{x}{4})$, où $α$ désigne le même paramètre scalaire que précédemment. Ce choix mène aux équations d'évolution suivantes : $$ \left\{ \begin{aligned} x'(t) &= \left( 1 - \frac{x(t)}{4} \right) x(t) - x(t)\,y(t), \\ y'(t) &= - y(t) + x(t)\,y(t). \end{aligned} \right. \tag{Modified-Lotka-Volterra} $$ Avec ce modèle, le nombre de lapins en l'absence de renards ne diverge plus dans la limite $t \to \infty$. Tout comme les équations de Lotka-Volterra classiques étudiées précédemment, les équations (Modified-Lotka-Volterra) peuvent s'écrire sous la forme d'une équation différentielle générique $\mathbf Z' = \mathbf g(t, \mathbf Z)$. Écrire la fonction $\mathbf g$ correspondante sous forme d'une fonction Julia.
function g(t, Z)
### BEGIN SOLUTION
x, y = Z
return [x*(1-x/4) - x * y, y * (x - 1)]
### END SOLUTION
end;
@assert g(1, [0, 1]) ≈ [0, -1]
@assert g(0, [4, 1]) ≈ [-4, 3]
@assert g(10, [8, 2]) ≈ [-24.0, 14.0]
Il est possible de montrer qu'avec ce nouveau modèle, pour toute condition initiale telle que $x₀ > 0$ et $y₀ > 0$, les populations de lapins et de renards tendent vers l'équilibre $(x_{\infty}, y_{\infty}) := \bigl( 1, \frac{3}{4} \bigr)$ dans la limite $t \to \infty$, comme illustré ci-dessous.
Δ, niter = .01, 8_000
p1 = my_plot_evolution(g, [1.0, .1], Δ, niter)
p2 = my_plot_evolution(g, [3.0, .1], Δ, niter)
p3 = my_plot_evolution(g, [0.5, .75], Δ, niter)
plot(p1, p2, p3; layout=(1, 3), size=(900, 300))
On suppose maintenant que les populations animales de l'île sont à l'équilibre pour $t < 0$, donc en particulier $x(0^-) = x_{\infty}$ et $y(0^-) = y_{\infty}$. Afin de repeupler les îles voisines, des lapins sont extraits de l'île considérée au temps $t = 0$.
Soucieux de préserver l'équilibre écologique de l'île, les habitants souhaitent déterminer la proportion maximale de lapins pouvant être extraite tout en s'assurant que la population de renards ne s'écarte jamais (pour aucun $t \geq 0$) de plus de 20% de sa valeur d'équilibre $y_{\infty} = \frac{3}{4}$.
Pour cela, commencer par écrire une fonction
max_fox_deviationafin d'estimer la déviation maximale de la population de renards, en proportion (la fonction devra donc retourner un réel compris entre 0 et 1).
function max_fox_deviation(x₀)
### BEGIN SOLUTION
x∞, y∞ = 1, .75
Z₀ = [x₀, y∞]
Δ, niter = .01, 10_000
ts, Zs = solve_ode(g, Z₀, Δ, niter)
ys = getindex.(Zs, 2)
return maximum(abs, ys .- y∞) / y∞
### END SOLUTION
end;
@assert max_fox_deviation(0.) ≈ 1.
@assert max_fox_deviation(1.) ≈ 0.
@assert max_fox_deviation(1.e-10) > 1.9
@assert round(max_fox_deviation(0.5), digits=3) ≈ 0.481
- Estimer graphiquement la valeur minimale de $x₀$ qui est admissible. On pourra utiliser les fonctions
hlineetvline(ou leurs homologueshline!etvline!) pour tracer des droites horizontales ou verticales.
### BEGIN SOLUTION
x = LinRange(0, 1, 1000)
plot(x, max_fox_deviation.(x), label=nothing)
hline!([.2], label=nothing)
vline!([.8], label=nothing)
ylims!(0, 2.)
### END SOLUTION
(Bonus) Ayant constaté à la question précédente l'existence d'un $x_0$ minimal satisfaisant la contrainte sur la population de renard, on se propose ici de déterminer cette valeur de manière très précise. Pour cela, on suggère de procéder par une méthode de Newton-Raphson s'appuyant sur les nombres duaux. Écrire une fonction
newton_raphson_dual(x, f, maxiter=100; ε = 1e-12)renvoyant une racine de la fonctionfen partant d'un point initialx.Indication (cliquer pour afficher)
On rappelle que l'on peut obtenir simultanément la valeur et la dérivée d'une fonction
fenxpar la méthode suivantey = f(ForwardDiff.Dual(x, 1.)) fx = y.value # renvoie f(x) dfx = y.partials[1] # renvoie f'(x)
function newton_raphson_dual(x, f; maxiter=100, ε = 1e-12)
### BEGIN SOLUTION
for i in 1:maxiter
y = f(ForwardDiff.Dual(x, 1.))
x -= y.value/y.partials[1]
norm(f(x)) < ε && return x
end
return nothing
### END SOLUTION
end;
@assert newton_raphson_dual(1, x -> x^2 - 2) ≈ √2
@assert newton_raphson_dual(-1, x -> x^2 - 2) ≈ -√2
@assert newton_raphson_dual(1, x -> x^3 - 2) ≈ cbrt(2)
@assert newton_raphson_dual(2, x -> cos(x) - .5) ≈ acos(.5)
- (Bonus) À l'aide des fonctions
max_fox_deviationetnewton_raphson_dual, déterminer la valeur minimale dex₀qui est acceptable, et vérifier cette solution en traçant un graphe présentant l'évolution des populations pour lex₀obtenu.
### BEGIN SOLUTION
x₀ = newton_raphson_dual(.8, x -> max_fox_deviation(x) - .2)
y₀ = .75
Δ, niter = .01, 10_000
my_plot_evolution(g, [x₀, y₀], Δ, niter)
hline!([.75*.8, .75*1.2], label="Maximum deviation allowed")
### END SOLUTION
@assert x₀ > 0.5
### BEGIN HIDDEN TESTS
@assert round.(x₀, digits=3) ≈ 0.789
### END HIDDEN TESTS