Skip to content

Commit 030230e

Browse files
committed
Added HW5 prototype.
1 parent 8ee12e2 commit 030230e

File tree

2 files changed

+176
-1
lines changed

2 files changed

+176
-1
lines changed

docs/src/lecture_05/hw.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,64 @@
1-
# Homework 5: Advanced benchmarking
1+
# Homework 5: Root finding of polynomials
2+
This homework should test your ability to use the knowledge of benchmarking, profiling and others to improve an existing implementation of root finding methods for polynomials. The provided [code](https://github.com/JuliaTeachingCTU/Scientific-Programming-in-Julia/blob/master/docs/src/lecture_05/root_finding.jl) is of questionable quality. In spite of the artificial nature, it should simulate a situation in which you may find yourself quite often, as it represents some intermediate step of going from a simple script to something, that starts to resemble a package.
3+
4+
```@raw html
5+
<div class="admonition is-category-homework">
6+
<header class="admonition-header">Homework (2 points)</header>
7+
<div class="admonition-body">
8+
```
9+
Use profiler on the `find_root` function to find a piece of unnecessary code, that takes more time than the computation itself. The finding of roots with the polynomial
10+
```math
11+
p(x) = (x - 3)(x - 2)(x - 1)x(x + 1)(x + 2)(x + 3) = x^7 - 14x^5 + 49x^3 - 36x
12+
```
13+
should not take more than `50μs` when running with the following parameters
14+
```julia
15+
atol = 1e-12
16+
maxiter = 100
17+
stepsize = 0.95
18+
19+
x₀ = find_root(p, Bisection(), -5.0, 5.0, maxiter, stepsize, atol)
20+
x₀ = find_root(p, Newton(), -5.0, 5.0, maxiter, stepsize, atol)
21+
x₀ = find_root(p, Secant(), -5.0, 5.0, maxiter, stepsize, atol)
22+
```
23+
24+
Remove obvious type instabilities in both `find_root` and `step!` functions. Each variable with "inferred" type `::Any` in `@code_warntype` will be penalized.
25+
26+
**HINTS**:
27+
- running the function repeatedly `1000x` helps in the profiler sampling
28+
- focus on parts of the code that may have been used just for debugging purposes
29+
30+
```@raw html
31+
</div></div>
32+
<details class = "solution-body" hidden>
33+
<summary class = "solution-header">Solution:</summary><p>
34+
```
35+
36+
Nothing to see here.
37+
38+
39+
```@raw html
40+
</p></details>
41+
```
42+
43+
# How to submit?
44+
Put the modified `root_finding.jl` code inside `hw.jl`. Zip only this file (not its parent folder) and upload it to BRUTE. Your file should not use any dependency other than those already present in the `root_finding.jl`.
45+
46+
47+
# Voluntary exercise
48+
```@raw html
49+
<div class="admonition is-category-exercise">
50+
<header class="admonition-header">Voluntary exercise</header>
51+
<div class="admonition-body">
52+
```
53+
54+
Use `Plots.jl` to plot the polynomial $p$ on the interval $[-5, 5]$ and visualize the progress/convergence of each method, with a dotted vertical line and a dot on the x-axis for each subsequent root approximation ``.
55+
56+
**HINTS**:
57+
- plotting scalar function `f` - `plot(r, f)`, where `r` is a range of `x` values at which we evaluate `f`
58+
- updating an existing plot - either `plot!(plt, ...)` or `plot!(...)`, in the former case the plot lives in variable `plt` whereas in the latter we modify some implicit global variable
59+
- plotting dots - for example with `scatter`/`scatter!`
60+
- `plot([(1.0,2.0), (1.0,3.0)], ls=:dot)` will create a dotted line from position `(x=1.0,y=2.0)` to `(x=1.0,y=3.0)`
61+
62+
```@raw html
63+
</div></div>
64+
```
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using LinearAlgebra
2+
using Printf
3+
4+
function _polynomial(a, x)
5+
accumulator = a[end] * one(x)
6+
for i in length(a)-1:-1:1
7+
accumulator = accumulator * x + a[i]
8+
end
9+
accumulator
10+
end
11+
12+
# definition of polynom
13+
struct Polynom{C}
14+
coefficients::C
15+
Polynom(coefficients::CC) where CC = coefficients[end] == 0 ? throw(ArgumentError("Coefficient of the highest exponent cannot be zero.")) : new{CC}(coefficients)
16+
end
17+
18+
# based on https://github.com/JuliaMath/Polynomials.jl
19+
function from_roots(roots::AbstractVector{T}; aₙ = one(T)) where {T}
20+
n = length(roots)
21+
c = zeros(T, n+1)
22+
c[1] = one(T)
23+
for j = 1:n
24+
for i = j:-1:1
25+
c[i+1] = c[i+1]-roots[j]*c[i]
26+
end
27+
end
28+
return Polynom(aₙ.*reverse(c))
29+
end
30+
31+
(p::Polynom)(x) = _polynomial(p.coefficients, x)
32+
degree(p::Polynom) = length(p.coefficients) - 1
33+
34+
function _derivativeof(p::Polynom)
35+
n = degree(p)
36+
n > 1 ? Polynom([(i - 1)*p.coefficients[i] for i in 2:n+1]) : error("Low degree of a polynomial.")
37+
end
38+
LinearAlgebra.adjoint(p::Polynom) = _derivativeof(p)
39+
40+
function Base.show(io::IO, p::Polynom)
41+
n = degree(p)
42+
a = reverse(p.coefficients)
43+
for (i, c) in enumerate(a[1:end-1])
44+
if (c != 0)
45+
c < 0 && print(io, " - ")
46+
c > 0 && i > 1 && print(io, " + ")
47+
print(io, "$(abs(c))x^$(n - i + 1)")
48+
end
49+
end
50+
c = a[end]
51+
c > 0 && print(io, " + $(c)")
52+
c < 0 && print(io, " - $(abs(c))")
53+
end
54+
55+
# default optimization parameters
56+
atol = 1e-12
57+
maxiter = 100
58+
stepsize = 0.95
59+
60+
# definition of optimization methods
61+
abstract type RootFindingMethod end
62+
struct Newton <: RootFindingMethod end
63+
struct Secant <: RootFindingMethod end
64+
struct Bisection <: RootFindingMethod end
65+
66+
init!(::Bisection, p, a, b) = sign(p(a)) != sign(p(b)) ? (a, b) : throw(ArgumentError("Signs at both ends are the same."))
67+
init!(::RootFindingMethod, p, a, b) = (a, b)
68+
69+
function step!(::Newton, poly::Polynom, xᵢ, step_size)
70+
_, x̃ = xᵢ
71+
dp = p'
72+
x̃, x̃ - step_size*p(x̃)/dp(x̃)
73+
end
74+
75+
function step!(::Secant, poly::Polynom, xᵢ, step_size)
76+
x, x̃ = xᵢ
77+
dpx = (p(x) - p(x̃))/(x - x̃)
78+
x̃, x̃ - stepsize*p(x̃)/dpx
79+
end
80+
81+
function step!(::Bisection, poly::Polynom, xᵢ, step_size)
82+
x, x̃ = xᵢ
83+
midpoint = (x + x̃)/2
84+
if sign(p(midpoint)) == sign(p(x̃))
85+
= midpoint
86+
else
87+
x = midpoint
88+
end
89+
x, x̃
90+
end
91+
92+
function find_root(p::Polynom, rfm=Newton, a=-5.0, b=5.0, max_iter=100, step_size=0.95, tol=1e-12)
93+
x, x̃ = init!(rfm, p, a, b)
94+
for i in 1:maxiter
95+
x, x̃ = step!(rfm, p, (x, x̃), step_size)
96+
val = p(x̃)
97+
@printf "x = %.5f | x̃ = %.5f | p(x̃) = %g\n" x x̃ val
98+
abs(val) < atol && return
99+
end
100+
println("Method did not converge in $(max_iter) iterations to a root within $(tol) tolerance.")
101+
return
102+
end
103+
104+
# test code
105+
poly = Polynom(rand(4))
106+
p = from_roots([-3, -2, -1, 0, 1, 2, 3])
107+
dp = p'
108+
p(3.0), dp(3.0)
109+
110+
x₀ = find_root(p, Bisection(), -5.0, 5.0)
111+
x₀ = find_root(p, Newton(), -5.0, 5.0)
112+
x₀ = find_root(p, Secant(), -5.0, 5.0)

0 commit comments

Comments
 (0)