Skip to content

Commit c4c6bb7

Browse files
chmerdonpjaap
authored andcommitted
Modifications for constraints for problems with multiple unknowns
Example285 now supports periodic boundary conditions CompressedRestriction needs dummy assemble! function (otherwise crashes when linear problem is solved again with same ProblemDescription but without providing old SolverConfiguration), improved show of Unknowns
1 parent 858fea1 commit c4c6bb7

11 files changed

Lines changed: 149 additions & 56 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# CHANGES
22

3+
## v.1.10.3
4+
5+
### Fixed
6+
- Restriction now also work for problems with more than one unknown
7+
8+
### Added
9+
- Restrictions are listed in show of ProblemDescription
10+
- CompressedRestriction has (empty) assemble! function to prevent crash in some cases
11+
- improved show of Unknowns
12+
313
## v.1.10.0
414

515
### Changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ExtendableFEM"
22
uuid = "a722555e-65e0-4074-a036-ca7ce79a4aed"
3-
version = "1.10.2"
3+
version = "1.10.3"
44
authors = ["Christian Merdon <merdon@wias-berlin.de>", "Patrick Jaap <patrick.jaap@wias-berlin.de>"]
55

66
[deps]

examples/Example285_CahnHilliard.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ end
4444
function main(;
4545
order = 2, # finite element order for c and μ
4646
nref = 4, # refinement level
47+
periodic = true,
4748
M = 1.0,
4849
λ = 1.0e-2,
4950
iterations_until_next_plot = 20,
@@ -68,6 +69,13 @@ function main(;
6869
assign_operator!(PD, BilinearOperator([id(μ)]; store = true))
6970
assign_operator!(PD, BilinearOperator([grad(μ)], [grad(c)]; factor = -λ, store = true))
7071

72+
if periodic
73+
assign_restriction!(PD, CoupledDofsRestriction(c, 1, 3))
74+
assign_restriction!(PD, CoupledDofsRestriction(c, 2, 4))
75+
assign_restriction!(PD, CoupledDofsRestriction(μ, 1, 3))
76+
assign_restriction!(PD, CoupledDofsRestriction(μ, 2, 4))
77+
end
78+
7179
## add nonlinear reaction part (= -df/dc times test function)
7280
function kernel_dfdc!(result, input, qpinfo)
7381
return result[1] = -dfdc(input[1])

src/common_restrictions/boundarydata_restriction.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ function BoundaryDataRestriction(u::Unknown, data = nothing; kwargs...)
2626
else
2727
boundary_operator = InterpolateBoundaryData(u, data; kwargs...)
2828
end
29-
return BoundaryDataRestriction(u, boundary_operator, Dict{Symbol, Any}(:name => "BoundaryDataRestriction"))
29+
return BoundaryDataRestriction(
30+
u, boundary_operator, Dict{Symbol, Any}(
31+
:name => "BoundaryDataRestriction",
32+
:unknown => u
33+
)
34+
)
3035
end
3136

3237

@@ -47,7 +52,7 @@ function assemble!(R::BoundaryDataRestriction, sol, SC; kwargs...)
4752
nvals = length(fixeddofs)
4853

4954
## assign fixed dofs and vals to restriction
50-
n = length(SC.b.entries)
55+
n = length(SC.b[R.parameters[:unknown]])
5156
R.parameters[:matrix] = sparse(fixeddofs, 1:nvals, ones(nvals), n, nvals)
5257
R.parameters[:rhs] = fixedvals
5358
R.parameters[:multiplier] = zeros(nvals)

src/common_restrictions/compressed_restriction.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,20 @@ end
1212
1313
This is 𝑛𝑜𝑡 exported, for internal use only!
1414
"""
15-
function CompressedRestriction(matrix::AbstractMatrix, rhs::AbstractVector)
15+
function CompressedRestriction(unknown::Union{Unknown, Int}, matrix::AbstractMatrix, rhs::AbstractVector)
1616
return CompressedRestriction(
1717
Dict{Symbol, Any}(
1818
:name => "CompressedRestriction",
19+
:unknown => unknown,
1920
:matrix => matrix,
2021
:rhs => rhs,
2122
:multiplier => zero(rhs)
2223
)
2324
)
2425
end
26+
27+
28+
function assemble!(R::CompressedRestriction, sol, SC; kwargs...)
29+
# do nothing
30+
return nothing
31+
end

src/common_restrictions/coupled_dofs_restriction.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ end
1515
1616
The matrix can be obtained from, e.g., `get_periodic_coupling_matrix`.
1717
"""
18-
function CoupledDofsRestriction(matrix::AbstractMatrix)
18+
function CoupledDofsRestriction(matrix::AbstractMatrix; unknown = 1)
1919
return CoupledDofsRestriction(
2020
[matrix],
2121
Dict{Symbol, Any}(
2222
:name => "CoupledDofsRestriction",
23+
:unknown => unknown,
2324
:reduce_col_space => false
2425
)
2526
)
@@ -34,11 +35,12 @@ end
3435
By default, the column space of the matrices is reduced to be of full rank.
3536
This can toggled by the `:reduce_col_space` parameter.
3637
"""
37-
function CoupledDofsRestriction(matrices::Vector{AM}) where {AM <: AbstractMatrix}
38+
function CoupledDofsRestriction(matrices::Vector{AM}; unknown = 1) where {AM <: AbstractMatrix}
3839
return CoupledDofsRestriction(
3940
matrices,
4041
Dict{Symbol, Any}(
4142
:name => "CoupledDofsRestriction",
43+
:unknown => unknown,
4244
:reduce_col_space => true
4345
)
4446
)
@@ -112,7 +114,7 @@ function assemble!(R::CoupledDofsRestriction, sol, SC; kwargs...)
112114
coupling_matrix = get_periodic_coupling_matrix(FES, source_region, target_region, give_opposite!; R_kwargs...)
113115

114116
# replace R (in this scope)
115-
R = CoupledDofsRestriction(coupling_matrix)
117+
R = CoupledDofsRestriction(coupling_matrix, unknown = R.parameters[:unknown])
116118
end
117119

118120

src/common_restrictions/linear_functional_restriction.jl

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Construct a linear functional restriction for a finite element unknown from a gi
1515
- `value::T`: The target value for the linear functional (default: `0`).
1616
1717
"""
18-
function LinearFunctionalRestriction(O::LinearOperator{Tv}; value::T = 0) where {Tv, T}
19-
return LinearFunctionalRestriction{T, Tv}(value, O, Dict{Symbol, Any}(:name => "LinearFunctionalRestriction"))
18+
function LinearFunctionalRestriction(u::Unknown, O::LinearOperator{Tv}; value::T = 0) where {Tv, T}
19+
return LinearFunctionalRestriction{T, Tv}(value, O, Dict{Symbol, Any}(:name => "LinearFunctionalRestriction", :unknown => u))
2020
end
2121

2222

@@ -37,7 +37,12 @@ Internally, it creates a `LinearOperator` using the provided kernel and operator
3737
"""
3838
function ZeroMeanValueRestriction(u::Unknown; kernel = ExtendableFEMBase.constant_one_kernel, operator = Identity, Tv = Float64)
3939
linear_operator = LinearOperator(kernel, [(u, operator)]; store = true, Tv)
40-
return LinearFunctionalRestriction{Int64, Tv}(0, linear_operator, Dict{Symbol, Any}(:name => "MeanValueRestriction"))
40+
return LinearFunctionalRestriction{Int64, Tv}(
41+
0, linear_operator, Dict{Symbol, Any}(
42+
:name => "MeanValueRestriction",
43+
:unknown => u
44+
)
45+
)
4146
end
4247

4348

@@ -59,20 +64,25 @@ Internally, it creates a `LinearOperator` using the provided kernel and operator
5964
"""
6065
function MassRestriction(u::Unknown; kernel = ExtendableFEMBase.constant_one_kernel, value::T = 0, operator = Identity, Tv = Float64) where {T}
6166
linear_operator = LinearOperator(kernel, [(u, operator)]; store = true, Tv)
62-
return LinearFunctionalRestriction{T, Tv}(value, linear_operator, Dict{Symbol, Any}(:name => "MeanValueRestriction"))
67+
return LinearFunctionalRestriction{T, Tv}(
68+
value, linear_operator, Dict{Symbol, Any}(
69+
:name => "MeanValueRestriction",
70+
:unknown => u
71+
)
72+
)
6373
end
6474

6575

6676
function assemble!(R::LinearFunctionalRestriction{T, Tv}, sol, SC; kwargs...) where {T, Tv}
6777

68-
n = length(SC.b.entries)
78+
n = length(SC.b[R.parameters[:unknown]])
6979

70-
b = deepcopy(SC.b)
80+
b = copy(SC.b)
7181
fill!(b.entries, 0.0)
7282

7383
assemble!(nothing, b, sol, R.linear_operator, SC; assemble_rhs = true, kwargs...)
7484

75-
R.parameters[:matrix] = sparse(reshape(b.entries, n, 1))
85+
R.parameters[:matrix] = sparse(reshape(view(b[R.parameters[:unknown]]), n, 1))
7686
R.parameters[:rhs] = Tv[R.value]
7787
R.parameters[:multiplier] = zeros(1)
7888

src/problemdescription.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,13 @@ function Base.show(io::IO, PD::ProblemDescription)
153153
println(io, " [$i] $o")
154154
end
155155
end
156+
println(io, " Restrictions ($(length(PD.restrictions))):")
157+
if isempty(PD.restrictions)
158+
println(io, " (none)")
159+
else
160+
for (i, r) in enumerate(PD.restrictions)
161+
println(io, " [$i] $r")
162+
end
163+
end
156164
return
157165
end

src/restrictions.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ abstract type AbstractRestriction end
77

88

99
function Base.show(io::IO, R::AbstractRestriction)
10-
print(io, "AbstractRestriction")
10+
if haskey(R.parameters, :name)
11+
print(io, "$(R.parameters[:name])")
12+
if haskey(R.parameters, :name)
13+
print(io, " for $(ansatz_function(R.parameters[:unknown]))")
14+
end
15+
else
16+
print(io, "Restriction of type $(typeof(R))")
17+
end
1118
return nothing
1219
end
1320

src/solvers.jl

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function compute_nonlinear_residual!(residual, A, b, sol, unknowns, PD, SC, free
4343

4444
# add Lagrange residuals
4545
for rs in PD.restrictions
46-
mul!(residual.entries, rs.parameters[:matrix], rs.parameters[:multiplier], -1.0, 1.0)
46+
mul!(view(residual[rs.parameters[:unknown]]), rs.parameters[:matrix], rs.parameters[:multiplier], -1.0, 1.0)
4747
end
4848

4949

@@ -59,14 +59,14 @@ function compute_nonlinear_residual!(residual, A, b, sol, unknowns, PD, SC, free
5959
end
6060

6161
nlres = length(freedofs) > 0 ? norm(residual.entries[freedofs]) : norm(residual.entries)
62-
restriction_residuals = [norm(rs.parameters[:matrix]' * sol.entries - rs.parameters[:rhs]) for rs in PD.restrictions]
62+
restriction_residuals = [norm(rs.parameters[:matrix]' * view(sol[rs.parameters[:unknown]]) - rs.parameters[:rhs]) for rs in PD.restrictions]
6363

6464
if length(PD.restrictions) > 0
6565
nlres = sqrt(nlres^2 + norm(restriction_residuals)^2)
6666
end
6767

6868
for rs in PD.restrictions
69-
mul!(residual.entries, rs.parameters[:matrix], rs.parameters[:multiplier], 1.0, 1.0)
69+
view(residual[rs.parameters[:unknown]]) .+= rs.parameters[:matrix] * rs.parameters[:multiplier]
7070
end
7171

7272
if SC.parameters[:verbosity] > 0
@@ -278,9 +278,6 @@ function solve_linear_system!(A, b, sol, soltemp, residual, unknowns, freedofs,
278278
b_unrestricted = residual.entries
279279
end
280280

281-
## restrictions only involve the blocks coressponding to the unknowns and not necessarily the full sol.entries
282-
sol_freedofs = vcat([view(sol[u]) for u in unknowns]...)
283-
284281
if length(PD.restrictions) == 0
285282
if linsolve_needs_matrix
286283
linsolve_A = A_unrestricted
@@ -306,68 +303,97 @@ function solve_linear_system!(A, b, sol, soltemp, residual, unknowns, freedofs,
306303
if linsolve_needs_matrix
307304
if SC.parameters[:compress_restrictions]
308305

306+
# compress only once and for all
307+
SC.parameters[:compress_restrictions] = false
308+
309309
SC.parameters[:verbosity] > 1 && @info "Compressing the restriction matrices..."
310310

311-
# combine all restriction matrices into one:
312-
combined_transposed_restriction_matrix = vcat(restriction_matrices'...)
313-
combined_restriction_rhs = vcat(restriction_rhss...)
311+
compressed_restrictions = []
312+
compressed_matrices = []
313+
compressed_rhss = []
314+
315+
# find all restricted unknowns
316+
restricted_unknowns = unique([ re.parameters[:unknown] for re in PD.restrictions])
317+
318+
for u in restricted_unknowns
314319

315-
# compute rank revealing QR decomposition (of the transposed matrix)
316-
qr_result = qr(combined_transposed_restriction_matrix)
320+
# which restrictions belong to `u`?
321+
indices = filter(i -> PD.restrictions[i].parameters[:unknown] == u, 1:length(PD.restrictions))
317322

318-
SC.parameters[:verbosity] > 1 && @info "QR decomposition computed"
323+
# combine all restriction matrices into one:
324+
combined_transposed_restriction_matrix = vcat(restriction_matrices[indices]'...)
325+
combined_restriction_rhs = vcat(restriction_rhss[indices]...)
319326

320-
# extract components (from docs: Q*R = combined_transposed_restriction_matrix[prow, pcol])
321-
(; Q, R, prow, pcol) = qr_result
327+
# compute rank revealing QR decomposition (of the transposed matrix)
328+
qr_result = qr(combined_transposed_restriction_matrix)
322329

323-
# we need the inverse column permutation
324-
ipcol = invperm(pcol)
330+
SC.parameters[:verbosity] > 1 && @info "QR decomposition computed"
325331

326-
# the new combined restriction block (add another transpose and convert into CSC matrix)
327-
combined_restriction_matrix = sparse(R[:, ipcol]')
332+
# extract components (from docs: Q*R = combined_transposed_restriction_matrix[prow, pcol])
333+
(; Q, R, prow, pcol) = qr_result
328334

329-
# the new combined restriction rhs
330-
combined_restriction_rhs = Q'combined_restriction_rhs[prow]
335+
# we need the inverse column permutation
336+
ipcol = invperm(pcol)
331337

332-
# compress the column space
333-
qr_rank = rank(qr_result)
334-
SC.parameters[:verbosity] > 1 && @info "QR decomposition has rank $qr_rank"
335-
@assert norm(combined_restriction_rhs[(qr_rank + 1):end]) 1.0e-12 * norm(combined_restriction_rhs) "the rhs of the restriction is not in the image"
338+
# the new combined restriction block (add another transpose and convert into CSC matrix)
339+
combined_restriction_matrix = sparse(R[:, ipcol]')
336340

337-
combined_restriction_matrix = combined_restriction_matrix[:, 1:qr_rank]
338-
combined_restriction_rhs = combined_restriction_rhs[1:qr_rank]
341+
# the new combined restriction rhs
342+
combined_restriction_rhs = Q'combined_restriction_rhs[prow]
339343

340-
# replace by single entries
341-
restriction_matrices = [combined_restriction_matrix]
342-
restriction_rhss = [combined_restriction_rhs]
344+
# compress the column space
345+
qr_rank = rank(qr_result)
346+
SC.parameters[:verbosity] > 1 && @info "QR decomposition has rank $qr_rank"
347+
@assert norm(combined_restriction_rhs[(qr_rank + 1):end]) 1.0e-12 * norm(combined_restriction_rhs) "the rhs of the restriction is not in the image"
343348

344-
# remove previously defined restrictions (with explicit copy of the rhs; it is otherwise modified later)
345-
PD.restrictions = [CompressedRestriction(combined_restriction_matrix, deepcopy(combined_restriction_rhs))]
349+
combined_restriction_matrix = combined_restriction_matrix[:, 1:qr_rank]
350+
combined_restriction_rhs = combined_restriction_rhs[1:qr_rank]
351+
352+
# replace by single entries
353+
push!(compressed_matrices, combined_restriction_matrix)
354+
push!(compressed_rhss, combined_restriction_rhs)
355+
356+
# explicit copy of the rhs; it is otherwise modified later
357+
compressed_restriction = CompressedRestriction(u, combined_restriction_matrix, deepcopy(combined_restriction_rhs))
358+
push!(compressed_restrictions, compressed_restriction)
359+
360+
SC.parameters[:verbosity] > 1 && @info "Created new CompressedRestriction for unknown $u"
361+
end
362+
363+
# remove previously defined restrictions
364+
PD.restrictions = compressed_restrictions
365+
366+
restriction_matrices = compressed_matrices
367+
restriction_rhss = compressed_rhss
346368

347369
# update sizes
348-
block_sizes = [length(b_unrestricted), length(combined_restriction_rhs)]
370+
block_sizes = [length(b_unrestricted), length.(compressed_rhss)...]
349371
block_ends = cumsum(block_sizes)
350-
351-
SC.parameters[:verbosity] > 1 && @info "Created new CompressedRestriction"
352372
end
353373

354374
linsolve_A = spzeros(Tv, block_ends[end], block_ends[end])
355375
linsolve_A[1:block_ends[1], 1:block_ends[1]] = A_unrestricted
356376
end
357377

378+
# corresponding sol entries for each restriction
379+
restricted_sol_entries = [view(sol[re.parameters[:unknown]]) for re in PD.restrictions]
380+
358381
## we need to add the (initial) solution to the rhs, since we work with the residual equation
359-
for (B, rhs) in zip(restriction_matrices, restriction_rhss)
360-
# rhs -= B'sol_freedofs
361-
mul!(rhs, B', sol_freedofs, -1.0, 1.0)
382+
for (B, rhs, sol_entries) in zip(restriction_matrices, restriction_rhss, restricted_sol_entries)
383+
# rhs -= B'sol_entries
384+
mul!(rhs, B', sol_entries, -1.0, 1.0)
362385
end
363386

364387
linsolve_b = zeros(Tv, block_ends[end])
365388
linsolve_b[1:block_ends[1]] = b_unrestricted
366389

367390
for i in eachindex(restriction_matrices)
391+
# index range of the restricted unknown
392+
dof_range = (sol[PD.restrictions[i].parameters[:unknown]].offset + 1):(sol[PD.restrictions[i].parameters[:unknown]].last_index)
393+
368394
if linsolve_needs_matrix
369-
linsolve_A[1:block_ends[1], (block_ends[i] + 1):block_ends[i + 1]] = restriction_matrices[i]
370-
linsolve_A[(block_ends[i] + 1):block_ends[i + 1], 1:block_ends[1]] = restriction_matrices[i]'
395+
linsolve_A[dof_range, (block_ends[i] + 1):block_ends[i + 1]] = restriction_matrices[i]
396+
linsolve_A[(block_ends[i] + 1):block_ends[i + 1], dof_range] = restriction_matrices[i]'
371397
end
372398
linsolve_b[(block_ends[i] + 1):block_ends[i + 1]] = restriction_rhss[i]
373399

0 commit comments

Comments
 (0)