Skip to content
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

Merged
merged 30 commits into from
Jul 24, 2023
Merged

Add defect control adaptivity #89

merged 30 commits into from
Jul 24, 2023

Conversation

ErikQQY
Copy link
Member

@ErikQQY ErikQQY commented May 29, 2023

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

Signed-off-by: ErikQQY <2283984853@qq.com>
@ErikQQY ErikQQY marked this pull request as draft May 29, 2023 13:04
src/solve.jl Outdated Show resolved Hide resolved
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>
@ErikQQY ErikQQY marked this pull request as ready for review June 11, 2023 08:09
ErikQQY and others added 3 commits June 11, 2023 16:21
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
src/solve.jl Outdated
Comment on lines 232 to 262
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
Copy link
Member

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?

Copy link
Member Author

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 $\frac{r1}{r3}$ with $\rho$ to decide whether we halve the mesh or make a redistribute.

    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
Comment on lines 173 to 180
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
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BVP book does have the specification of r1, r2 and r3

image

But both the book and the paper don't elaborate on the setting of the other parameters, so I set these parameters according to the FORTRAN routine.

src/solve.jl Outdated
info = -1
else
println("Mesh redistributing")
mesh_new = redistribute(mesh_current, n, Nsub_star, s_hat)
Copy link
Member

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?

Copy link
Member Author

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this global?

Copy link
Member Author

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>
@ChrisRackauckas
Copy link
Member

What's left to complete this? Looks like there's some merge issues that are just from the formatter.

@ErikQQY
Copy link
Member Author

ErikQQY commented Jun 24, 2023

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 k_discrete, thus the defect we finally get maybe result in a different outcome.

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>
@ErikQQY ErikQQY changed the title [WIP] Add defect control adaptivity Add defect control adaptivity Jul 10, 2023
@@ -17,22 +17,26 @@ function alg_cache{T,U}(alg::MIRK4, S::BVPSystem{T,U})
end
=#

struct MIRK4GeneralCache{kType} <: GeneralMIRKCache
Copy link
Member

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.

Copy link
Member Author

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🤔.

Copy link
Member

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>
Signed-off-by: ErikQQY <2283984853@qq.com>
return length(mesh) - 1
else
ind = findfirst(x -> x >= t, mesh)
i::Int64 = copy(ind)
Copy link
Member

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?

Copy link
Member

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

Comment on lines +51 to +59
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
Copy link
Member

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?

src/adaptivity.jl Outdated Show resolved Hide resolved
src/adaptivity.jl Outdated Show resolved Hide resolved
# Mesh redistribution fails
#println("New mesh would be too large")
info = ReturnCode.Failure
mesh_new = Nothing
Copy link
Member

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,
Copy link
Member

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]
Copy link
Member

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.

src/adaptivity.jl Outdated Show resolved Hide resolved
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

src/adaptivity.jl Outdated Show resolved Hide resolved
Comment on lines +194 to +197
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)
Copy link
Member

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Copy link
Member Author

@ErikQQY ErikQQY Jul 18, 2023

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tm1 = tau - 1.0
tm1 = tau - 1

Copy link
Member

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

src/adaptivity.jl Outdated Show resolved Hide resolved
Signed-off-by: ErikQQY <2283984853@qq.com>
Signed-off-by: ErikQQY <2283984853@qq.com>
@ChrisRackauckas
Copy link
Member

Needs to rebase for the MIRK5, then I think we can merge and continue some of this in a follow up.

ErikQQY and others added 2 commits July 23, 2023 11:30
Signed-off-by: ErikQQY <2283984853@qq.com>
@ErikQQY
Copy link
Member Author

ErikQQY commented Jul 24, 2023

@ChrisRackauckas bump

@ChrisRackauckas
Copy link
Member

Open issues for the points that weren't addressed, but let's go for it. 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉

@ChrisRackauckas ChrisRackauckas merged commit e697350 into SciML:master Jul 24, 2023
4 of 6 checks passed
@ErikQQY ErikQQY mentioned this pull request Jul 24, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Adaptivity in MIRK solver Defect control adaptivity
3 participants