Skip to content

Commit 5b0ce16

Browse files
pjaapchmerdon
andauthored
Lagrange Restrictions (#79)
adds restrictions to ProblemDescription to enforce constraints like zero mean value, coupled dofs and boundary data by Lagrange multipliers Co-authored-by: chmerdon <[email protected]>
1 parent ce1b3a8 commit 5b0ce16

16 files changed

+536
-55
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
### Added
66

77
- `compute_periodic_coupling_matrix` can be computed thread parallel
8+
- new "Restriction" objects using Lagrange multipliers internally
9+
- user can apply linear `LinearFunctionalRestriction`, `ZeroMeanValueRestriction`, `MassRestriction`, `CoupledDofsRestriction` or `BoundaryDataRestriction` to the `ProblemDescription` by `apply_restriction!()`
10+
- Incorporated new restriction objects in examples 201, 252, 312 and 212.
811

912
## v1.4.0
1013

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ version = "1.5.0"
44
authors = ["Christian Merdon <[email protected]>", "Patrick Jaap <[email protected]>"]
55

66
[deps]
7+
BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e"
78
ChunkSplitters = "ae650224-84b6-46f8-82ea-d812ca08434e"
89
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
910
DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
1011
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1112
ExtendableFEMBase = "12fb9182-3d4c-4424-8fd1-727a0899810c"
1213
ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
1314
ExtendableSparse = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3"
15+
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
1416
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1517
GridVisualize = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
1618
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -26,6 +28,7 @@ UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
2628

2729
[compat]
2830
Aqua = "0.8"
31+
BlockArrays = "1.7.0"
2932
ChunkSplitters = "3.1.2"
3033
CommonSolve = "0.2"
3134
DiffResults = "1"
@@ -35,6 +38,7 @@ ExplicitImports = "1"
3538
ExtendableFEMBase = "1.3.0"
3639
ExtendableGrids = "1.10.3"
3740
ExtendableSparse = "1.5.3"
41+
FillArrays = "1.13.0"
3842
ForwardDiff = "0.10.35,1"
3943
GridVisualize = "1.8.1"
4044
IncompleteLU = "0.2.1"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function make_all(; with_examples::Bool = true, modules = :all, run_examples::Bo
8282
"nonlinearoperator.md",
8383
"bilinearoperator.md",
8484
"linearoperator.md",
85+
"restrictions.md",
8586
"interpolateboundarydata.md",
8687
"homogeneousdata.md",
8788
"fixdofs.md",

docs/src/restrictions.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Restrictions
2+
3+
ExtendableFEM.jl provides functionality to apply various restrictions to your finite element problems through the `AbstractRestriction` type system. Restrictions enforce additional constraints on your solution that cannot be easily expressed through standard boundary conditions or the weak formulation.
4+
5+
## Built-in Restrictions
6+
7+
### Linear Functional Restriction
8+
9+
`LinearFunctionalRestriction` allows you to constrain a linear functional of a finite element unknown to a specific value. This is useful, for example, for enforcing that the solution has mean zero, or that an integral constraint is satisfied.
10+
11+
Documentation:
12+
```@autodocs
13+
Modules = [ExtendableFEM]
14+
Pages = ["common_restrictions/linear_functional_restriction.jl"]
15+
Order = [:type, :function]
16+
```
17+
18+
#### Example Usage
19+
```julia
20+
# Restrict the mean value of unknown u to 0
21+
restriction = ZeroMeanValueRestriction(u)
22+
23+
# Restrict the mean value to 1.0 with a specific operator
24+
restriction = MassRestriction(u, value = 1.0, operator = MyCustomOperator)
25+
26+
# Assign to the problem
27+
assign_restriction!(PD, restriction)
28+
```
29+
30+
### Coupled DOFs Restriction
31+
32+
`CoupledDofsRestriction` enables coupling between different degrees of freedom (DOFs) in your system. This is particularly useful for implementing periodic boundary conditions or other constraints where DOFs need to be related to each other. Compared to manipulating the system matrix directly via operators (e.g. `CombineDofs`), `CoupledDofsRestriction` is much faster for large systems.
33+
34+
Documentation:
35+
```@autodocs
36+
Modules = [ExtendableFEM]
37+
Pages = ["common_restrictions/coupled_dofs_restriction.jl"]
38+
Order = [:type, :function]
39+
```
40+
41+
#### Example Usage
42+
```julia
43+
# Create a coupling matrix for periodic boundary conditions
44+
coupling_matrix = get_periodic_coupling_matrix(...)
45+
restriction = CoupledDofsRestriction(coupling_matrix)
46+
assign_restriction!(PD, restriction)
47+
```
48+
49+
### Boundary Data Restriction
50+
51+
`BoundaryDataRestriction` enforces prescribed boundary values for a finite element unknown. It can be used for both homogeneous (zero) and non-homogeneous boundary conditions, interpolating the boundary data as needed.
52+
53+
Documentation:
54+
```@autodocs
55+
Modules = [ExtendableFEM]
56+
Pages = ["common_restrictions/boundarydata_restriction.jl"]
57+
Order = [:type, :function]
58+
```
59+
60+
#### Example Usage
61+
```julia
62+
# Homogeneous boundary data (default)
63+
restriction = BoundaryDataRestriction(u)
64+
65+
# Inhomogeneous boundary data using a function
66+
restriction = BoundaryDataRestriction(u, data = (result, qpinfo) -> result .= sin(qpinfo.x[1]))
67+
68+
assign_restriction!(PD, restriction)
69+
```
70+
71+
## AbstractRestriction API
72+
73+
All restrictions are subtypes of `AbstractRestriction`. The following functions are available for interacting with restrictions:
74+
75+
- `assemble!(restriction, solution, SC; kwargs...)`: Assemble the internal matrices and vectors for the restriction.
76+
- `restriction_matrix(restriction)`: Get the restriction matrix.
77+
- `restriction_rhs(restriction)`: Get the right-hand side vector for the restriction.
78+
- `fixed_dofs(restriction)`: Get the list of fixed degrees of freedom affected by the restriction.
79+
- `is_timedependent(restriction)`: Returns whether the restriction is time-dependent.
80+
81+
---

examples/Example201_PoissonProblem.jl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,27 @@ function default_f!(fval, qpinfo)
3535
return nothing
3636
end
3737

38-
function main(; μ = 1.0, nrefs = 4, order = 2, Plotter = nothing, parallel = false, f! = default_f!, npart = parallel ? 8 : 1, kwargs...)
38+
function main(;
39+
μ = 1.0,
40+
nrefs = 4,
41+
order = 2,
42+
f! = default_f!,
43+
use_restriction = true,
44+
parallel = false,
45+
npart = parallel ? 8 : 1,
46+
Plotter = nothing,
47+
kwargs...
48+
)
3949
## problem description
4050
PD = ProblemDescription()
4151
assign_unknown!(PD, u)
4252
assign_operator!(PD, BilinearOperator([grad(u)]; parallel = parallel, factor = μ, kwargs...))
4353
assign_operator!(PD, LinearOperator(f!, [id(u)]; parallel = parallel, kwargs...))
44-
assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4))
54+
if use_restriction
55+
assign_restriction!(PD, BoundaryDataRestriction(u; regions = 1:4, value = 0))
56+
else
57+
assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4))
58+
end
4559

4660
## discretize
4761
xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs)
@@ -65,6 +79,8 @@ function runtests() #hide
6579
@test sum(sol.entries) 1.1140313632246377 #hide
6680
sol_parallel, plt = main(; μ = 1.0, nrefs = 2, order = 2, parallel = true, npart = 2) #hide
6781
@assert sum((sol_parallel.entries .- sol.entries) .^ 2) 0.0 #hide
82+
sol_restrict, plt = main(; μ = 1.0, nrefs = 2, use_restriction = false, parallel = false, order = 2, npart = 2) #hide
83+
@assert sqrt(sum((sol_restrict.entries .- sol.entries) .^ 2)) < 1.0e-15 #hide
6884
return nothing #hide
6985
end #hide
7086
end # module

examples/Example212_PeriodicBoundary2D.jl

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ end
116116
function main(;
117117
order = 1,
118118
periodic = true,
119+
use_LM_restrictions = true,
119120
Plotter = nothing,
120121
force = 10.0,
121122
h = 1.0e-2,
@@ -154,8 +155,12 @@ function main(;
154155
return nothing
155156
end
156157

157-
@time coupling_matrix = get_periodic_coupling_matrix(FES, reg_left, reg_right, give_opposite!; parallel = threads > 1, threads)
158-
assign_operator!(PD, CombineDofs(u, u, coupling_matrix; kwargs...))
158+
@showtime coupling_matrix = get_periodic_coupling_matrix(FES, reg_left, reg_right, give_opposite!; parallel = threads > 1, threads)
159+
if use_LM_restrictions
160+
assign_restriction!(PD, CoupledDofsRestriction(coupling_matrix))
161+
else
162+
assign_operator!(PD, CombineDofs(u, u, coupling_matrix; kwargs...))
163+
end
159164
end
160165

161166
sol = solve(PD, FES)
@@ -172,10 +177,15 @@ end
172177

173178
generateplots = ExtendableFEM.default_generateplots(Example212_PeriodicBoundary2D, "example212.png") #hide
174179
function runtests() #hide
175-
sol, _ = main() #hide
176-
@test abs(maximum(view(sol[1])) - 1.3447465095618172) < 1.0e-3 #hide
177-
sol2, _ = main(threads = 4) #hide
178-
@test sol.entries sol2.entries #hide
180+
sol1, _ = main(use_LM_restrictions = false, threads = 1) #hide
181+
@test abs(maximum(view(sol1[1])) - 1.3447465095618172) < 1.0e-3 #hide
182+
183+
sol2, _ = main(use_LM_restrictions = false, threads = 4) #hide
184+
@test sol1.entries sol2.entries #hide
185+
186+
sol3, _ = main(use_LM_restrictions = true, threads = 4) #hide
187+
@test sol1.entries sol3.entries #hide
188+
179189
return nothing #hide
180190
end #hide
181191

examples/Example252_NSEPlanarLatticeFlow.jl

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,14 @@ function exact_error!(result, u, qpinfo)
8383
return result .= result .^ 2
8484
end
8585

86-
function main(; μ = 0.001, nrefs = 5, reconstruct = true, Plotter = nothing, kwargs...)
86+
function main(;
87+
μ = 0.001,
88+
nrefs = 5,
89+
reconstruct = true,
90+
Plotter = nothing,
91+
use_LM_restrictions = true,
92+
kwargs...
93+
)
8794

8895
## problem description
8996
PD = ProblemDescription()
@@ -96,6 +103,11 @@ function main(; μ = 0.001, nrefs = 5, reconstruct = true, Plotter = nothing, kw
96103
assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u, grad(u), id(p)]; params = [μ], kwargs...))
97104
assign_operator!(PD, LinearOperator(f!(μ), [id_u]; kwargs...))
98105
assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4))
106+
if use_LM_restrictions
107+
assign_restriction!(PD, ZeroMeanValueRestriction(p))
108+
else
109+
assign_operator!(PD, FixDofs(p; dofs = [1]))
110+
end
99111

100112
## grid
101113
xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs)
@@ -106,10 +118,14 @@ function main(; μ = 0.001, nrefs = 5, reconstruct = true, Plotter = nothing, kw
106118
## solve
107119
sol = solve(PD, FES; kwargs...)
108120

109-
## move integral mean of pressure
110121
pintegrate = ItemIntegrator([id(p)])
111122
pmean = sum(evaluate(pintegrate, sol)) / sum(xgrid[CellVolumes])
112-
view(sol[p]) .-= pmean
123+
if use_LM_restrictions
124+
@show pmean
125+
else
126+
## move integral mean of pressure
127+
view(sol[p]) .-= pmean
128+
end
113129

114130
## error calculation
115131
ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), id(p)]; quadorder = 4, params = [μ], kwargs...)
@@ -127,7 +143,9 @@ end
127143

128144
generateplots = ExtendableFEM.default_generateplots(Example252_NSEPlanarLatticeFlow, "example252.png") #hide
129145
function runtests() #hide
130-
L2errorU, plt = main(; nrefs = 4) #hide
146+
L2errorU, _ = main(; use_LM_restrictions = false, nrefs = 4) #hide
147+
@test L2errorU 0.010616923333947861 #hide
148+
L2errorU, _ = main(; use_LM_restrictions = true, nrefs = 4) #hide
131149
@test L2errorU 0.010616923333947861 #hide
132150
return nothing #hide
133151
end #hide

examples/Example312_PeriodicBoundary3D.jl

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ end
130130
function main(;
131131
order = 1,
132132
periodic = true,
133+
use_LM_restrictions = true,
133134
Plotter = nothing,
134135
force = 1.0,
135136
h = 1.0e-4,
@@ -169,8 +170,12 @@ function main(;
169170
y[1] = width - x[1]
170171
return nothing
171172
end
172-
@time coupling_matrix = get_periodic_coupling_matrix(FES, reg_left, reg_right, give_opposite!; parallel = threads > 1, threads)
173-
assign_operator!(PD, CombineDofs(u, u, coupling_matrix; kwargs...))
173+
@showtime coupling_matrix = get_periodic_coupling_matrix(FES, reg_left, reg_right, give_opposite!; parallel = threads > 1, threads)
174+
if use_LM_restrictions
175+
assign_restriction!(PD, CoupledDofsRestriction(coupling_matrix))
176+
else
177+
assign_operator!(PD, CombineDofs(u, u, coupling_matrix; kwargs...))
178+
end
174179
end
175180

176181
## solve
@@ -185,10 +190,15 @@ end
185190

186191
generateplots = ExtendableFEM.default_generateplots(Example312_PeriodicBoundary3D, "example312.png") #hide
187192
function runtests() #hide
188-
sol, _ = main() #hide
189-
@test abs(maximum(view(sol[1])) - 1.8004602502175202) < 2.0e-3 #hide
190-
sol2, _ = main(threads = 4) #hide
191-
@test sol.entries sol2.entries #hide
193+
sol1, _ = main(use_LM_restrictions = false, threads = 1) #hide
194+
@test abs(maximum(view(sol1[1])) - 1.8004602502175202) < 2.0e-3 #hide
195+
196+
sol2, _ = main(use_LM_restrictions = false, threads = 4) #hide
197+
@test sol1.entries sol2.entries #hide
198+
199+
sol3, _ = main(use_LM_restrictions = true, threads = 4) #hide
200+
@test sol1.entries sol3.entries #hide
201+
192202
return nothing #hide
193203
end #hide
194204

src/ExtendableFEM.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ $(read(joinpath(@__DIR__, "..", "README.md"), String))
66
module ExtendableFEM
77

88
using ChunkSplitters: chunks
9+
using BlockArrays: BlockMatrix, BlockVector, Block, blocks, axes, mortar
910
using CommonSolve: CommonSolve
1011
using DiffResults: DiffResults
1112
using DocStringExtensions: DocStringExtensions, TYPEDEF, TYPEDSIGNATURES
@@ -60,6 +61,7 @@ using ExtendableGrids: ExtendableGrids, AT_NODES, AbstractElementGeometry,
6061
using ExtendableSparse: ExtendableSparse, ExtendableSparseMatrix, flush!,
6162
MTExtendableSparseMatrixCSC, findindex,
6263
rawupdateindex!
64+
using FillArrays: Zeros
6365
using ForwardDiff: ForwardDiff
6466
using GridVisualize: GridVisualize, GridVisualizer, gridplot!, reveal, save,
6567
scalarplot!, vectorplot!
@@ -68,7 +70,7 @@ using LinearSolve: LinearSolve, LinearProblem, UMFPACKFactorization, deleteat!,
6870
init, solve
6971
using Printf: Printf, @printf, @sprintf
7072
using SparseArrays: SparseArrays, AbstractSparseArray, SparseMatrixCSC, findnz, nnz,
71-
nzrange, rowvals, sparse, SparseVector
73+
nzrange, rowvals, sparse, SparseVector, spzeros
7274
using StaticArrays: @MArray
7375
using SparseDiffTools: SparseDiffTools, ForwardColorJacCache,
7476
forwarddiff_color_jacobian!, matrix_colors
@@ -123,11 +125,14 @@ include("common_operators/reduction_operator.jl")
123125
#export AbstractReductionOperator
124126
#export FixbyInterpolation
125127

128+
include("restrictions.jl")
129+
126130
include("problemdescription.jl")
127131
export ProblemDescription
128132
export assign_unknown!
129133
export assign_operator!
130134
export replace_operator!
135+
export assign_restriction!
131136

132137
include("helper_functions.jl")
133138
export get_periodic_coupling_info
@@ -196,6 +201,15 @@ export FixDofs
196201
include("common_operators/discface_interpolator.jl")
197202
export FaceInterpolator
198203

204+
include("common_restrictions/boundarydata_restriction.jl")
205+
export BoundaryDataRestriction
206+
include("common_restrictions/coupled_dofs_restriction.jl")
207+
export CoupledDofsRestriction
208+
include("common_restrictions/linear_functional_restriction.jl")
209+
export LinearFunctionalRestriction
210+
export ZeroMeanValueRestriction
211+
export MassRestriction
212+
199213
include("plots.jl")
200214
export plot_convergencehistory!
201215
export scalarplot!

src/common_operators/homogeneousdata_operator.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mutable struct HomogeneousData{UT, AT} <: AbstractOperator
77
end
88

99
fixed_dofs(O::HomogeneousData) = O.bdofs
10-
fixed_vals(O::HomogeneousData) = O.parameters[:value]
10+
fixed_vals(O::HomogeneousData) = O.parameters[:value] * ones(length(fixed_dofs(O)))
1111

1212

1313
default_homdata_kwargs() = Dict{Symbol, Tuple{Any, String}}(
@@ -143,6 +143,7 @@ function assemble!(O::HomogeneousData{UT, AT}, FES = O.FES; offset = 0, kwargs..
143143
end
144144
end
145145
end
146+
unique!(bdofs)
146147
if O.parameters[:verbosity] > 0
147148
@info "$(O.parameters[:name]) : penalizing $(length(bdofs)) dofs of '$(O.u.name)' ($AT, regions = $(O.parameters[:regions]))"
148149
end

0 commit comments

Comments
 (0)