-
Notifications
You must be signed in to change notification settings - Fork 4
/
IGA.jl
473 lines (364 loc) · 16.8 KB
/
IGA.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
module IGA
# import dependent packages
using FastGaussQuadrature, PyCall, PyPlot
# export types
export Degree, KnotVector, BezierPatch, NURBS, QuadRule
# export functionality from other libs
export ndgrid, allcomb, linearspace, boundary
export buildvector, uniqueknots, globalrefine, findspan
export dimsplinespace, grevillepoints, onebasisfuneval, bsplinebasiseval, dersbsplinebasisfuns, dersbsplinebasisfunsinterpolationmatrix
export knotinsertionoperator!, knotinsertionoperator, degreeelevationoperator, bezierdecompoperator
export dim
# export functionality from nurbs.jl
export nsdim, nbezierpatches, ienarray
export refine!, globalrefine!, degreeelevate!
export boundary, ∂
# export functionality from this module
export dersbezierbasisfuns, assembly, plot, plot_stress_contours, L2residual
include("functions/baseextension.jl")
include("functions/knotvector.jl")
include("functions/bsplinebasisfuns.jl")
include("functions/knotrefinement.jl")
include("functions/nurbs.jl")
include("functions/quadrature.jl")
######################################################################################################
####################################### Shape function routines ######################################
######################################################################################################
# bernstein basis functions on [-1,1] and their derivatives
dersbezierbasisfuns(p::Int, u::Float64, nout::Int) = dersbsplinebasisfuns(p, buildvector([-1.0;1.0],[p+1;p+1]), p+1, u, nout)'
# bernstein basis functions on [-1,1] and their derivatives at a set of points u
function dersbezierbasisfuns(p::Int, u::Vector{Float64}, nout::Int)
ϕ = zeros(p+1,length(u),nout)
for k in 1:length(u)
ϕ[:,k,:] = dersbezierbasisfuns(p, u[k], nout)
end
return ϕ
end
# Compute 1D shape function and derivatives in a single point
function dersbezierbasisfuns(S::BezierPatch{1}, ϕ::Vector{Float64}, dϕ::Vector{Float64})
# initialize
C = S.dec
W = S.wpts
P = S.cpts
# NURBS are B-splines in projective three-space
Bʷ = broadcast(*, W, C[1]'* ϕ)
∇Bʷ = broadcast(*, W, C[1]'* dϕ)
# compute weightfunction and derivative
w = sum(Bʷ)
∇w = sum(∇Bʷ)
# compute NURBS basisfunction and derivative
R = Bʷ / w
∇R = (∇Bʷ * w - Bʷ * ∇w) / w^2
# return quadtities in physical space
return R, ∇R
end
# Compute 1D shape function and derivatives in an array of points
function dersbezierbasisfuns(S::BezierPatch{1}, ϕ::Matrix{Float64}, dϕ::Matrix{Float64})
R, ∇R = zeros(ϕ), zeros(dϕ)
for k in 1:size(R,2)
R[:,k], ∇R[:,k] = dersbezierbasisfuns(S, ϕ[:,k], dϕ[:,k])
end
return R, ∇R
end
# Compute 2D shape function, derivatives and determinant in a single point
function dersbezierbasisfuns(S::BezierPatch{2}, ϕ::NTuple{2,Vector{Float64}}, dϕ::NTuple{2,Vector{Float64}})
# initialize
C = S.dec
W = S.wpts
P = S.cpts
# compute B-spline basis functions
ϕ = ntuple(k-> C[k]'* ϕ[k], 2)
dϕ = ntuple(k-> C[k]'*dϕ[k], 2)
# NURBS are B-splines in projective 3-space
Bʷ = broadcast(*, W, kron(ϕ[2], ϕ[1]))
∇Bʷ = broadcast(*, W, [kron(ϕ[2], dϕ[1]) kron(dϕ[2], ϕ[1])])
# compute weightfunction
w = sum(Bʷ)
∇w = sum(∇Bʷ, 1)
# compute NURBS functions and derivatives
R = Bʷ / w
∇R = (∇Bʷ * w - Bʷ * ∇w) / w^2
# return quadtities in physical space
return R, ∇R
end
# Compute 2D shape function and derivatives in an array of points
function dersbezierbasisfuns(S::BezierPatch{2}, ϕ::NTuple{2,Matrix{Float64}}, dϕ::NTuple{2,Matrix{Float64}})
n = prod([size(ϕ[k],1) for k in 1:2]); m = ntuple(k -> size(ϕ[k],2), 2)
R, ∇R = zeros(n,prod(m)), zeros(n,prod(m),2)
i = 1
for k in 1:m[2]
for l in 1:m[1]
R[:,i], ∇R[:,i,:] = dersbezierbasisfuns(S, (ϕ[1][:,k], ϕ[2][:,l]), (dϕ[1][:,k], dϕ[2][:,l]))
i+=1
end
end
return R, ∇R
end
######################################################################################################
########################################## Assembly routines #########################################
######################################################################################################
typealias Forcing Function
typealias Traction Function
typealias BoundaryDisplacement Function
typealias Material Function
function assembly(S::NURBS, D::Material, f::Forcing, g::BoundaryDisplacement, h::Traction)
######################### Initialize #############################
p = S.deg # polynomial degree
nsd = nsdim(S) # number of space dimensions
ndofs = dimsplinespace(S) # dimension of the spline space
dims = nbezierpatches(S) # number of elements in each direction
IEN = S.ien # set IEN-array
ID = S.id # set ID-array
neq = maximum(ID) # number of equations
K = spzeros(neq,neq) # allocate space for the stiffness matrix
F = zeros(neq) # allocate space for the righthandside forcing
######################## Precompute data #########################
# precompute Gauss-Legendre nodes and weights
qr = ntuple(k -> QuadRule(p[k]+1,"legendre"), nsd)
# pre-compute univariate Bernstein polynomials and their derivatives
ϕ = ntuple(k -> dersbezierbasisfuns(p[k], qr[k].points, 2), nsd)
######################## Assembly loop over elements #########################
# integration over domain Ω
for e in 1:size(IEN,2)
# initialize
A = IEN[:,e] # global node numbers
P = ID[:,A] # global equation numbers
i,j = ind2sub(dims,e) # element subscripts
# construct Bezier Patch
Sb = BezierPatch(tuple(p...), S.cpts[A,:], S.wpts[A], (S.dec[1][:,:,i], S.dec[2][:,:,j]))
# compute element contribution of the stiffness matrix and the forcing vector
Kₑ, Fₑ = element_stiffness_and_forcing(Sb, ϕ, qr, D, f)
# add contributions of element stiffness matrix and right hand side forcing
I = P.!=0 # index internal displacements
K[P[I],P[I]] += Kₑ[I[:],I[:]]
F[P[I]] += Fₑ[I[:]]
# subtract Dirichlet boundary displacements
for i in 1:2
for a in 1:size(P,2)
if P[i,a]==0
F[P[I]] -= Kₑ[I,a] * g(vec(S.cpts[A[a],:]))[i]
end
end
end
end
# integration over boundary ∂Ω
for dir = 1:2
I = 1:nsd.!=dir
for k in 1:2
C = ∂(S,dir,k) # boundary k, direction dir, of S
for e in 1:size(C.ien,2)
# initialize
A = C.ien[:,e] # global node numbers
P = ID[:,A][:] # global equation numbers
J = P.!=0 # dofs with Neumann data
# construct Bezier Patch
Cb = BezierPatch(tuple(C.deg...), S.cpts[A,:], S.wpts[A], (C.dec[1][:,:,e],))
# compute element contribution of the stiffness matrix and the forcing vector
F[P[J]] += neumann_boundary_condition(Cb, ϕ[I], qr[I], h)[J]
end
end
end
return K, F
end
# computation of element stiffness and forcing
function element_stiffness_and_forcing(Sb::BezierPatch{2}, ϕ::NTuple{2,Array{Float64,3}}, qr::NTuple{2,QuadRule}, D::Material, f::Forcing)
# initialize
nen = prod(Sb.deg+1) # number of element nodes
ned = 2 # number of element dofs per node
Kₑ = zeros(nen*ned, nen*ned) # allocate space for element stiffness matrix
Fₑ = zeros(nen*ned) # allocate space for element forcing vector
# loop over quadrature points
for j in 1:dim(qr[2])
for i in 1:dim(qr[1])
# shape function routine
R, ∇R = dersbezierbasisfuns(Sb,(ϕ[1][:,i,1],ϕ[2][:,j,1]), (ϕ[1][:,i,2],ϕ[2][:,j,2]))
# compute geometrical properties
J = ∇R' * Sb.cpts # Jacobian
x = vec(R' * Sb.cpts) # quadrature point in physical space
α = qr[1].weights[i]*qr[2].weights[j]*det(J) # quadrature weight in physical space
# compute strain components
N = (inv(J) * ∇R')'
B = Matrix{Float64}[[N[a,1] 0.0;
0.0 N[a,2];
N[a,2] N[a,1]] for a in 1:nen]
# compute element stiffness contribution of quadrature point i,j
for a in 1:nen
p = ned*(a-1)+(1:ned)
for b in a:nen
q = ned*(b-1)+(1:ned)
Kₑ[p,q] += (B[a]' * D(x) * B[b]) * α
end
end
# compute element forcing contribution of quadrature point i,j
Fₑ += ((f(x) * α) * R')[:]
end
end
# Use symmetry to determine the remainder of the element stiffness matrix
for a in 1:nen
p = ned*(a-1)+(1:ned)
for b in a+1:nen
q = ned*(b-1)+(1:ned)
Kₑ[q,p] = Kₑ[p,q]'
end
end
return Kₑ, Fₑ
end
# computation of element tractions
function neumann_boundary_condition(Cb::BezierPatch{1}, ϕ::Tuple{Array{Float64,3}}, qr::Tuple{QuadRule}, h::Function)
# initialize
nen = prod(Cb.deg+1) # number of element nodes
ned = 2 # number of element dofs per node
Fₑ = zeros(nen*ned) # allocate space for element forcing vector
# loop over quadrature points
for i in 1:dim(qr[1])
# shape function routine
R, ∇R = dersbezierbasisfuns(Cb, ϕ[1][:,i,1], ϕ[1][:,i,2])
# compute geometrical properties
t = vec(∇R' * Cb.cpts) # Tangent vector
x = vec(R' * Cb.cpts) # quadrature point in physical space
α = qr[1].weights[i]*norm(t) # quadrature weight in physical space
# compute element forcing contribution of quadrature point i,j
Fₑ += ((h(x) * α) * R')[:]
end
return Fₑ
end
######################################################################################################
########################################## Postprocessing routines ###################################
######################################################################################################
import PyPlot.plot
# plot the NURBS geometry
function plot(S::NURBS, gran=(3,3))
# initialize
if isempty(S.ien)==true
S.ien = ienarray(S)
end
if isempty(S.dec[1])==true
S.dec = bezierdecompoperator(S)
end
dims = ntuple(i -> size(S.dec[i],3), 2)
u = ntuple(i -> linearspace(-1.0,1.0,gran[i]), 2)
ϕ = ntuple(i -> dersbezierbasisfuns(S.deg[i], u[i], 2), 2)
# loop over elements
for e in 1:prod(dims)
i,j = ind2sub(dims,e) # element subscripts
A = S.ien[:,e] # global node numbers
# construct Bezier Patch
Sb = BezierPatch(tuple(S.deg...), S.cpts[A,:], S.wpts[A], (S.dec[1][:,:,i], S.dec[2][:,:,j]))
# compute 2-dimensional rational basis functions
R, ∇R = dersbezierbasisfuns(Sb, (ϕ[1][:,:,1], ϕ[2][:,:,1]), (ϕ[1][:,:,2], ϕ[2][:,:,2]))
# plot surface
X = ntuple(i -> reshape(R' * Sb.cpts[:,i], gran), size(Sb.cpts,2))
contourf(X[1], X[2], zeros(X[1]))
# plot boundary
plot(vec(X[1][:,1]), vec(X[2][:,1]),"k")
plot(vec(X[1][:,end]), vec(X[2][:,end]),"k")
plot(vec(X[1][1,:]), vec(X[2][1,:]),"k")
plot(vec(X[1][end,:]), vec(X[2][end,:]),"k")
end
end
# plot the contourlevels of stress
function plot_stress_contours(G::NURBS{2}, S::NURBS{2}, ii::Int, D::Material, levels::Vector{Float64}, gran=(4,4))
# initialize
p = G.deg
nen = prod(p+1)
dims = ntuple(i -> size(S.dec[i],3), 2)
# sample points
u = ntuple(k -> linearspace(-1.0,1.0,gran[k]), 2)
# pre-compute univariate Bernstein polynomials and their derivatives
ϕ = ntuple(k -> dersbezierbasisfuns(p[k], u[k], 2), 2)
# loop over elements
for e in 1:size(S.ien,2)
i,j = ind2sub(dims,e) # element subscripts
A = G.ien[:,e] # global node numbers
# construct Bezier Patch
Sb = BezierPatch(tuple(p...), G.cpts[A,:], G.wpts[A], (G.dec[1][:,:,i], G.dec[2][:,:,j]))
# compute the stress at the sample points
σd = zeros(gran[1],gran[2],3)
X = zeros(gran); Y = zeros(gran)
for j in 1:gran[2]
for i in 1:gran[1]
# shape function routine
R, ∇R = dersbezierbasisfuns(Sb,(ϕ[1][:,i,1],ϕ[2][:,j,1]), (ϕ[1][:,i,2],ϕ[2][:,j,2]))
# compute geometrical properties
J = ∇R' * Sb.cpts # Jacobian
x = vec(R' * Sb.cpts) # quadrature point in physical space
if det(J)==0.0
J = eye(2)
end
# compute strain basis functions
N = (inv(J) * ∇R')'
B = Matrix{Float64}[[N[a,1] 0.0;
0.0 N[a,2];
N[a,2] N[a,1]] for a in 1:nen]
# compute the stress in point x
for a in 1:nen
save = D(x)*B[a]*S.cpts[A[a],:]'
for k in 1:3
σd[i,j,k] += save[k]
end
end
# save the coordinates
X[i,j] = x[1]; Y[i,j] = x[2]
end
end
cp = contourf(X, Y, σd[:,:,ii],levels)
if e==1
colorbar(cp)
end
end
end
typealias Geometry NURBS{2}
typealias Solution NURBS{2}
typealias ExactSolution Function
# Compute the L2 residial in stress
function L2residual(G::Geometry, S::Solution, σ::ExactSolution, D::Material)
# initialize
p = S.deg # polynomial degree
nsd = nsdim(S) # number of space dimensions
nen = prod(p+1)
ndofs = dimsplinespace(S) # dimension of the spline space
dims = nbezierpatches(S) # number of elements in each direction
IEN = S.ien # set IEN-array
# precompute Gauss-Legendre nodes and weights
qr = ntuple(k -> QuadRule(p[k]+1,"legendre"), nsd)
# pre-compute univariate Bernstein polynomials and their derivatives
ϕ = ntuple(k -> dersbezierbasisfuns(p[k], qr[k].points, 2), nsd)
# integration over domain Ω
L²res = zeros(3)
L21 = zeros(3)
L22 = zeros(3)
for e in 1:size(IEN,2)
# initialize
A = IEN[:,e] # global node numbers
i,j = ind2sub(dims,e) # element subscripts
# construct Bezier Patch
Sb = BezierPatch(tuple(p...), G.cpts[A,:], G.wpts[A], (G.dec[1][:,:,i], G.dec[2][:,:,j]))
# loop over quadrature points
for j in 1:dim(qr[2])
for i in 1:dim(qr[1])
# shape function routine
R, ∇R = dersbezierbasisfuns(Sb,(ϕ[1][:,i,1],ϕ[2][:,j,1]), (ϕ[1][:,i,2],ϕ[2][:,j,2]))
# compute geometrical properties
J = ∇R' * Sb.cpts # Jacobian
x = vec(R' * Sb.cpts) # quadrature point in physical space
α = qr[1].weights[i]*qr[2].weights[j]*det(J) # quadrature weight in physical space
# compute strain components
N = (inv(J) * ∇R')'
# compute the stress in point x
σd = zeros(3)
for a in 1:nen
# strain basis function `a'
B = [N[a,1] 0.0;
0.0 N[a,2];
N[a,2] N[a,1]]
# compute the stress in quadrature point i,j
σd += D(x) * B * S.cpts[A[a],:]'
end
# compute element forcing contribution of quadrature point i,j
L²res += (σd - σ(x)).^2 * α
end
end
end
return sqrt(L²res)
end
end