-
-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add defect control adaptivity #89
Conversation
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
src/solve.jl
Outdated
function redistribute(mesh_current::Vector, | ||
n::Int64, | ||
Nsub_star::Int64, | ||
s_hat::Vector{Float64}) | ||
mesh_new = zeros(Float64, Nsub_star + 1) | ||
sum = 0.0 | ||
for k in 1:n | ||
sum += s_hat[k] * (mesh_current[k + 1] - mesh_current[k]) | ||
end | ||
zeta = sum / Nsub_star | ||
k::Int64 = 1 | ||
i::Int64 = 0 | ||
mesh_new[1] = mesh_current[1] | ||
t = mesh_current[1] | ||
integral::Float64 = 0.0 | ||
while k <= n | ||
next_piece = s_hat[k] * (mesh_current[k + 1] - t) | ||
if (integral + next_piece) > zeta | ||
mesh_new[i + 2] = (zeta - integral) / s_hat[k] + t | ||
t = mesh_new[i + 2] | ||
i += 1 | ||
integral = 0 | ||
else | ||
integral += next_piece | ||
t = mesh_current[k + 1] | ||
k += 1 | ||
end | ||
end | ||
mesh_new[Nsub_star + 1] = mesh_current[n + 1] | ||
return mesh_new | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you describe what this one is doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The redistribute part generates a new mesh to make sure that on the next iteration, the new solution on the new mesh would have a smaller defect estimation.
Based on the judging quantities we computed before using defect and mesh function values, we compare the ratio
s_hat = zeros(Float64, n)
for i in 1:n
h = mesh_current[i + 1] - mesh_current[i]
norm = abs(defect[i, idamax(defect[i, :])])
s_hat[i] = (norm / tol)^(1.0 / (p + 1)) / h
if s_hat[i] * h > r1
r1 = s_hat[i] * h
end
r2 = r2 + s_hat[i] * h
end
r3 = r2 / n
n_predict::Int64 = round(Int, (safety_factor * r2) + 1)
if abs((n_predict - n) / n) < 0.1
n_predict = round(Int, 1.1 * n)
end
If we need to redistribute(r1/r3>rho), we redistribute a new mesh, I keep some detailed notes on the redistribute process here: https://erikqqy.github.io/2023/06/04/gsoc-1st-week/#Redistribute-mesh
src/solve.jl
Outdated
safety_factor = 1.3 | ||
rho = 1.0 # Set rho=1 means mesh distribution will take place everytime. | ||
upper_new_mesh = 4.0 | ||
lower_new_mesh = 0.5 | ||
r1 = 0.0 | ||
r2 = 0.0 | ||
Nsub_star = 0 | ||
info = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these pulled from the paper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/solve.jl
Outdated
info = -1 | ||
else | ||
println("Mesh redistributing") | ||
mesh_new = redistribute(mesh_current, n, Nsub_star, s_hat) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't undersatnd why one would redistribute. Wouldn't this make it so you aren't clustering the points where the error is the most anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my understanding, after we redistribute the mesh and by using the new mesh to generate a new discrete solution, the new solution would have a smaller error.
src/solve.jl
Outdated
|
||
# Change the original solution Vector{Vector{Float64}} to a matrix | ||
# has size of (number of subintervals, length of equation) | ||
global Y = transpose(reduce(hcat, S.y)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this global?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The discrete solution Y
is a local variable in the loop of iteration, so I set Y
as global so we can finally build a solution based on Y
.
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
What's left to complete this? Looks like there's some merge issues that are just from the formatter. |
The main issue here is that the defect norm from the defect estimation part is always larger than the threshold, making the redistribution part hit an endless loop. I think that the main problem can be located in the non-linear solving part of BundaryValueDiffEq.jl and MIRKDC, they are using different Newton iteration techniques, which leads to the difference in discrete stages |
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
@@ -17,22 +17,26 @@ function alg_cache{T,U}(alg::MIRK4, S::BVPSystem{T,U}) | |||
end | |||
=# | |||
|
|||
struct MIRK4GeneralCache{kType} <: GeneralMIRKCache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this to be mutable? It seems that the array elements are being updated later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for i in 1:(N - 1)
h = x[i + 1] - x[i]
# Update K
update_K!(S, cache, TU, i, h)
for r in 1:s
x_new = x[i] + c[r] * h
y_new = (1 - v[r]) * y[i] + v[r] * y[i + 1]
if r > 1
y_new += h * sum(j -> X[r, j] * K[j], 1:(r - 1))
end
temp = zeros(Float64, M)
fun!(temp, y_new, S.p, x_new)
K[r] = temp[:]
end
# Update residual
residual[i] = y[i + 1] - y[i] - h * sum(j -> b[j] * K[j], 1:s)
cache.k_discrete[i, :] = K[:]
end
The MIRK cache stores the discrete stages from evaluating PHI
in the nonlinear solving process. Since we only need discrete stages from the final nonlinear solving step, I think the previous steps can be omitted, but I didn't figure out an executable way, e.g. judging if this step is the last time we evaluate PHI
🤔.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's mutating the values but not the cache itself
Signed-off-by: ErikQQY <2283984853@qq.com>
return length(mesh) - 1 | ||
else | ||
ind = findfirst(x -> x >= t, mesh) | ||
i::Int64 = copy(ind) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does this need a declaration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh the nothing case. Ehh that's kind of not nice but it works
MxNsub = 3000 | ||
|
||
safety_factor = 1.3 | ||
rho = 1.0 # Set rho=1 means mesh distribution will take place everytime. | ||
upper_new_mesh = 4.0 | ||
lower_new_mesh = 0.5 | ||
r1 = 0.0 | ||
r2 = 0.0 | ||
Nsub_star = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surface things as kwargs in a follow up?
# Mesh redistribution fails | ||
#println("New mesh would be too large") | ||
info = ReturnCode.Failure | ||
mesh_new = Nothing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use the datatype, that won't specialize
Generate a new mesh. | ||
""" | ||
function redistribute(mesh_current::Vector, | ||
n::Int64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why require Int64?
k += 1 | ||
end | ||
end | ||
mesh_new[Nsub_star + 1] = mesh_current[n + 1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will fail on 32-bit OS with the coercion to Int64.
mesh_new[1] = mesh_current[1] | ||
for i in 1:n | ||
mesh_new[2 * i + 1] = mesh_current[i + 1] | ||
mesh_new[2 * i] = (mesh_current[i + 1] + mesh_current[i]) / 2.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mesh_new[2 * i] = (mesh_current[i + 1] + mesh_current[i]) / 2.0 | |
mesh_new[2 * i] = (mesh_current[i + 1] + mesh_current[i]) / 2 |
f_sample_1, f_sample_2 = zeros(Float64, len), zeros(Float64, len) | ||
def_1, def_2 = zeros(Float64, len), zeros(Float64, len) | ||
temp_1, temp_2 = zeros(Float64, len), zeros(Float64, len) | ||
estimate_1, estimate_2 = zeros(Float64), zeros(Float64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assumes Float64
# Evaluate at the first sample point | ||
weights_1, weights_1_prime = interp_weights(tau_star, alg) | ||
# Evaluate at the second sample point | ||
weights_2, weights_2_prime = interp_weights(1.0 - tau_star, alg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weights_2, weights_2_prime = interp_weights(1.0 - tau_star, alg) | |
weights_2, weights_2_prime = interp_weights(1 - tau_star, alg) |
etc. shouldn't assume float64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think while we can know the type of tau_star
, why don't we directly use Float to avoid redundant type conversion?
function interp_weights(tau, alg::Union{GeneralMIRK, MIRK}) | ||
if alg_order(alg) == 4 | ||
t2 = tau * tau | ||
tm1 = tau - 1.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tm1 = tau - 1.0 | |
tm1 = tau - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Etc., shouldn't assume Float64
Signed-off-by: ErikQQY <2283984853@qq.com>
Needs to rebase for the MIRK5, then I think we can merge and continue some of this in a follow up. |
@ChrisRackauckas bump |
Open issues for the points that weren't addressed, but let's go for it. 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 |
Fix: #12
Fix: #54
Relating reference: https://netlib.org/ode/mirkdc.f
I just built the basic structure for the defect control adaptivity, it still needs a lot of work to get this program working.
The detailed explanation can be viewed on my blog post
ping @ChrisRackauckas @YingboMa @avik-pal