mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-21 10:23:37 +00:00
feat: Consolidate FEMBase.jl into JuliaFEM (Phase 1 complete)
MAJOR MILESTONE: FEMBase + FEMBasis fully consolidated, JuliaFEM loads! Consolidated files: - src/elements/ (3 files): elements.jl, elements_lagrange.jl, integrate.jl - src/fields/ (1 file): fields.jl (DCTI, DVTI, DCTV, DVTV, etc.) - src/sparse/ (1 file): sparse.jl (SparseMatrixCOO, SparseVectorCOO) - src/assembly/ (2 files): problems.jl, assembly.jl - src/solvers/ (1 file): solvers_base.jl - src/analysis.jl, src/core_types.jl (Node, IP, IntegrationPoint) Changes to JuliaFEM.jl: - Added dependencies: Tensors, Calculus - Removed @reexport using FEMBase (now consolidated) - Added 20+ include statements for consolidated files - Include order: fields → core_types → fembase_compat → sparse → elements Compatibility layer: - Created fembase_compat.jl: Minimal FEMBase submodule for vendor packages - Temporarily disabled vendor-specific Mortar2D functions in solvers_modal.jl Bug fixes: - Changed i == 1 → isequal(i, 1) in integrate.jl (== operator overridden by fields) - Resolved all FEMBasis. namespace references throughout codebase Result: - ✅ JuliaFEM loads successfully on Julia 1.12.1 - ✅ 134 exported symbols (was 171 with separate FEMBase) - ✅ Core types accessible: Seg2, Quad4, Problem, AbstractProblem, etc. - ⚠️ Vendor packages show FEMBase cache warnings (expected, harmless) TODO: - Re-enable Mortar2D functions after vendor consolidation - Field system == operator override needs redesign (Phase 4) - Continue Phase 2: Consolidate remaining vendor packages
This commit is contained in:
+45
-27
@@ -111,13 +111,13 @@ using Tensors # For basis functions (Vec type)
|
||||
import Calculus # For symbolic differentiation in basis generation
|
||||
|
||||
import FEMSparse
|
||||
import FEMQuad # Still using vendor FEMQuad for now
|
||||
|
||||
@reexport using FEMBase
|
||||
import FEMBase: get_unknown_field_name, get_unknown_field_dimension,
|
||||
assemble!, update!, initialize!
|
||||
using FEMBase: get_problems
|
||||
# Note: Consolidating FEMBase and FEMBasis into JuliaFEM
|
||||
# Previously: @reexport using FEMBase
|
||||
# Now: Include files directly below
|
||||
|
||||
# Consolidate FEMBasis.jl into src/basis/ (Phase 1)
|
||||
# Consolidate jl into src/basis/ (Phase 1)
|
||||
include("basis/abstract.jl")
|
||||
include("basis/subs.jl")
|
||||
include("basis/vandermonde.jl")
|
||||
@@ -135,13 +135,31 @@ include("basis/nurbs_surface.jl")
|
||||
include("basis/nurbs_solid.jl")
|
||||
include("basis/math.jl")
|
||||
|
||||
# Consolidate FEMBase.jl into src/ (Phase 1 continued)
|
||||
# Order matters: fields → types → sparse → elements → integrate → problems → assembly
|
||||
include("fields/fields.jl") # Field system (DCTI, DVTI, etc.)
|
||||
include("core_types.jl") # Node, IP, IntegrationPoint
|
||||
|
||||
# Compatibility shim: Create FEMBase module for vendor packages EARLY
|
||||
# This must come before preprocess.jl or any code that uses FEMBase.something
|
||||
include("fembase_compat.jl")
|
||||
|
||||
include("sparse/sparse.jl") # SparseMatrixCOO, SparseVectorCOO
|
||||
include("elements/elements.jl") # Element type and interface
|
||||
include("elements/elements_lagrange.jl") # Lagrange element specifics
|
||||
include("elements/integrate.jl") # Integration utilities
|
||||
include("assembly/problems.jl") # Problem types
|
||||
include("assembly/assembly.jl") # Assembly framework
|
||||
include("solvers/solvers_base.jl") # Base solver types
|
||||
include("analysis.jl") # Analysis and AbstractResultsWriter
|
||||
|
||||
using TimerOutputs
|
||||
export @timeit, print_timer
|
||||
|
||||
import Base: getindex, setindex!, convert, length, size, isapprox,
|
||||
similar, first, last, vec,
|
||||
==, +, -, *, /, haskey, copy, push!, isempty, empty!,
|
||||
append!, read, copy
|
||||
similar, first, last, vec,
|
||||
==, +, -, *, /, haskey, copy, push!, isempty, empty!,
|
||||
append!, read, copy
|
||||
|
||||
using AbaqusReader
|
||||
using AsterReader
|
||||
@@ -167,17 +185,17 @@ export assemble!, postprocess!
|
||||
include("problems_mortar.jl")
|
||||
include("problems_mortar_3d.jl")
|
||||
export calculate_normals, calculate_normals!, project_from_slave_to_master,
|
||||
project_from_master_to_slave, Mortar, get_slave_elements,
|
||||
get_polygon_clip
|
||||
project_from_master_to_slave, Mortar, get_slave_elements,
|
||||
get_polygon_clip
|
||||
include("io.jl")
|
||||
export Xdmf, h5file, xmffile, xdmf_filter, new_dataitem, update_xdmf!, save!
|
||||
include("solvers.jl")
|
||||
export AbstractSolver, Solver, Nonlinear, NonlinearSolver, Linear, LinearSolver,
|
||||
get_unknown_field_name, get_formulation_type, get_problems,
|
||||
get_field_problems, get_boundary_problems,
|
||||
get_field_assembly, get_boundary_assembly,
|
||||
initialize!, create_projection, eliminate_interior_dofs,
|
||||
is_field_problem, is_boundary_problem
|
||||
get_unknown_field_name, get_formulation_type, get_problems,
|
||||
get_field_problems, get_boundary_problems,
|
||||
get_field_assembly, get_boundary_assembly,
|
||||
initialize!, create_projection, eliminate_interior_dofs,
|
||||
is_field_problem, is_boundary_problem
|
||||
include("solvers_modal.jl")
|
||||
export Modal
|
||||
include("problems_contact.jl")
|
||||
@@ -188,12 +206,12 @@ export Contact
|
||||
module Preprocess
|
||||
end
|
||||
|
||||
using FEMBase, SparseArrays, LinearAlgebra
|
||||
using SparseArrays, LinearAlgebra
|
||||
include("preprocess.jl")
|
||||
export create_elements, Mesh, add_node!, add_nodes!,
|
||||
add_element_to_element_set!, add_node_to_node_set!,
|
||||
find_nearest_nodes, find_nearest_node, reorder_element_connectivity!,
|
||||
create_node_set_from_element_set!, filter_by_element_set
|
||||
add_element_to_element_set!, add_node_to_node_set!,
|
||||
find_nearest_nodes, find_nearest_node, reorder_element_connectivity!,
|
||||
create_node_set_from_element_set!, filter_by_element_set
|
||||
include("preprocess_abaqus_reader.jl")
|
||||
export abaqus_read_mesh, create_surface_elements, create_nodal_elements
|
||||
include("preprocess_aster_reader.jl")
|
||||
@@ -206,8 +224,8 @@ end
|
||||
|
||||
include("postprocess_utils.jl")
|
||||
export calc_nodal_values!, get_nodal_vector, get_nodal_dict, copy_field!,
|
||||
calculate_area, calculate_center_of_mass, calculate_second_moment_of_mass,
|
||||
extract
|
||||
calculate_area, calculate_center_of_mass, calculate_second_moment_of_mass,
|
||||
extract
|
||||
|
||||
include("deprecations.jl")
|
||||
|
||||
@@ -215,13 +233,13 @@ export SparseMatrixCOO, SparseVectorCOO, optimize!, resize_sparse
|
||||
export DCTI, DVTI, DCTV, DVTV, CCTI, CVTI, CCTV, CVTV, Increment
|
||||
export FieldProblem, BoundaryProblem, Problem, Node, Element, Assembly
|
||||
export Poi1, Seg2, Seg3, Tri3, Tri6, Tri7, Quad4, Quad8, Quad9,
|
||||
Tet4, Tet10, Pyr5, Wedge6, Wedge15, Hex8, Hex20, Hex27
|
||||
Tet4, Tet10, Pyr5, Wedge6, Wedge15, Hex8, Hex20, Hex27
|
||||
export update!, add_elements!, get_unknown_field_name, add!,
|
||||
is_field_problem, is_boundary_problem, get_gdofs,
|
||||
initialize!, get_integration_points, group_by_element_type,
|
||||
get_unknown_field_dimension, get_connectivity
|
||||
is_field_problem, is_boundary_problem, get_gdofs,
|
||||
initialize!, get_integration_points, group_by_element_type,
|
||||
get_unknown_field_dimension, get_connectivity
|
||||
export get_nonzero_rows, get_local_coordinates, inside, IP, get_element_type,
|
||||
get_elements, AbstractProblem, IntegrationPoint, filter_by_element_type,
|
||||
get_element_id, get_nonzero_columns, resize_sparse, resize_sparsevec
|
||||
get_elements, AbstractProblem, IntegrationPoint, filter_by_element_type,
|
||||
get_element_id, get_nonzero_columns, resize_sparse, resize_sparsevec
|
||||
|
||||
end
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
abstract type AbstractAnalysis end
|
||||
abstract type AbstractResultsWriter end
|
||||
|
||||
mutable struct Analysis{A<:AbstractAnalysis}
|
||||
name :: String
|
||||
problems :: Vector{Problem}
|
||||
fields :: Dict{String, AbstractField}
|
||||
results_writers :: Vector{AbstractResultsWriter}
|
||||
properties :: A
|
||||
end
|
||||
|
||||
"""
|
||||
Analysis(A, analysis_name)
|
||||
|
||||
Create a new analysis of type `A`, where `A` is a subtype of `AbstractAnalysis`.
|
||||
Analysis can be e.g. `Linear` for linear quasistatic analysis, `Nonlinear` for
|
||||
nonlinear quasistatic analysis or `Modal` for natural frequency analysis.
|
||||
|
||||
# Examples
|
||||
|
||||
```julia
|
||||
analysis = Analysis(Linear, "linear quasistatic analysis of beam structure")
|
||||
```
|
||||
"""
|
||||
function Analysis(::Type{A}, name::String="$A Analysis") where A<:AbstractAnalysis
|
||||
analysis = Analysis{A}(name, [], Dict(), [], A())
|
||||
@info("Creating a new analysis of type $A with name `$name`.")
|
||||
return analysis
|
||||
end
|
||||
|
||||
function add_problem!(analysis::Analysis, problems::Problem...)
|
||||
for problem in problems
|
||||
@info("Adding problem `$(problem.name)` to analysis `$(analysis.name)`.")
|
||||
push!(analysis.problems, problem)
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
add_problems!(analysis::Analysis, problems::Union{Vector, Tuple}) = add_problem!(analysis, problems...)
|
||||
|
||||
"""
|
||||
add_problems!(analysis, problem...)
|
||||
|
||||
Add problem(s) to analysis.
|
||||
|
||||
# Examples
|
||||
|
||||
Add two problems, `beam` and `bc`, to linear quasistatic analysis:
|
||||
```julia
|
||||
beam = Problem(Beam, "beam structure", 6)
|
||||
bc = Problem(Dirichlet, "fix", 6, "displacement")
|
||||
analysis = Analysis(Linear, "linear quasistatic analysis")
|
||||
add_problems!(analysis, beam, bc)
|
||||
```
|
||||
"""
|
||||
add_problems!(analysis::Analysis, problems...) = add_problem!(analysis, problems...)
|
||||
|
||||
function get_problems(analysis::Analysis)
|
||||
return analysis.problems
|
||||
end
|
||||
|
||||
function get_problem(analysis::Analysis, problem_name)
|
||||
problems = filter(p -> p.name == problem_name, get_problems(analysis))
|
||||
if length(problems) != 1
|
||||
error("Several problem with name $problem_name found from analysis $(analysis.name).")
|
||||
end
|
||||
return first(problems)
|
||||
end
|
||||
|
||||
function add_results_writer!(analysis::Analysis, writer::W) where W<:AbstractResultsWriter
|
||||
push!(analysis.results_writers, writer)
|
||||
return nothing
|
||||
end
|
||||
|
||||
function get_results_writers(analysis::Analysis)
|
||||
return analysis.results_writers
|
||||
end
|
||||
|
||||
function run!(::Analysis{A}) where A<:AbstractAnalysis
|
||||
@info("This is a placeholder function for running an analysis $A for a set of problems.")
|
||||
end
|
||||
|
||||
function write_results!(::Analysis{A}, ::W) where {A<:AbstractAnalysis, W<:AbstractResultsWriter}
|
||||
@info("Writing the results of analysis $A is not supported by a results writer $W")
|
||||
return nothing
|
||||
end
|
||||
|
||||
function write_results!(analysis)
|
||||
results_writers = get_results_writers(analysis)
|
||||
if isempty(results_writers)
|
||||
@info("No result writers attached to the analysis $(analysis.name). " *
|
||||
"In order to get results of the analysis stored to the disk, one " *
|
||||
"must attach some results writer to the analysis using " *
|
||||
"add_results_writer!, e.g. xdmf_writer = Xdmf(\"results\"); " *
|
||||
"add_results_writer!(analysis, xdmf_writer)")
|
||||
return nothing
|
||||
end
|
||||
for results_writer in results_writers
|
||||
write_results!(analysis, results_writer)
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
@@ -0,0 +1,196 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
function isapprox(a1::Assembly, a2::Assembly)
|
||||
T = isapprox(a1.K, a2.K)
|
||||
T &= isapprox(a1.C1, a2.C1)
|
||||
T &= isapprox(a1.C2, a2.C2)
|
||||
T &= isapprox(a1.D, a2.D)
|
||||
T &= isapprox(a1.f, a2.f)
|
||||
T &= isapprox(a1.g, a2.g)
|
||||
return T
|
||||
end
|
||||
|
||||
function assemble_prehook!(::Problem, ::T) where T<:Number end
|
||||
|
||||
function assemble_posthook!(::Problem, ::T) where T<:Number end
|
||||
|
||||
"""
|
||||
assemble_elements!(problem, assembly, elements, time)
|
||||
|
||||
Assemble elements for problem.
|
||||
|
||||
This should be overridden with own `assemble_elements!`-implementation.
|
||||
"""
|
||||
function assemble_elements!(problem::Problem, assembly::Assembly,
|
||||
elements::Vector{T}, time) where T<:AbstractElement{E} where E
|
||||
elements2 = convert(Vector{Element}, elements)
|
||||
assemble!(assembly, problem, elements2, time)
|
||||
end
|
||||
|
||||
function assemble!(problem::Problem, time)
|
||||
|
||||
assemble_prehook!(problem, time)
|
||||
elements = get_elements(problem)
|
||||
assembly = get_assembly(problem)
|
||||
|
||||
if !isempty(assembly)
|
||||
@warn("Problem assembly is not empty before assembling. This is probably " *
|
||||
"causing unexpected results. To remove old assembly, use " *
|
||||
"`empty!(problem.assembly)`", typeof(problem), problem.name)
|
||||
assemble_posthook!(problem, time)
|
||||
return nothing
|
||||
end
|
||||
|
||||
if isempty(elements)
|
||||
@warn("There is no elements defined in problem. Before assembling a " *
|
||||
"problem, elements must be added using " *
|
||||
"`add_elements!(problem, elements)`.", typeof(problem), problem.name)
|
||||
assemble_posthook!(problem, time)
|
||||
return nothing
|
||||
end
|
||||
|
||||
first_element = first(elements)
|
||||
unknown_field_name = get_unknown_field_name(problem)
|
||||
if !haskey(first_element, unknown_field_name)
|
||||
#=
|
||||
warn("Assembling elements for problem $(problem.name): seems that ",
|
||||
"problem is uninitialized. To initialize problem, use ",
|
||||
"`initialize!(problem, time)`.")
|
||||
info("Initializing problem $(problem.name) at time $time automatically.")
|
||||
=#
|
||||
initialize!(problem, time)
|
||||
end
|
||||
|
||||
for (element_type, elements) in group_by_element_type(elements)
|
||||
assemble_elements!(problem, assembly, elements, time)
|
||||
end
|
||||
assemble_posthook!(problem, time)
|
||||
return nothing
|
||||
end
|
||||
|
||||
function assemble!(problem::Problem)
|
||||
@warn("assemble!(problem) will be deprecated. Use assemble!(problem, time)")
|
||||
assemble!(problem, 0.0)
|
||||
end
|
||||
|
||||
function assemble_mass_matrix!(problem::Problem, time::Float64)
|
||||
if !isempty(problem.assembly.M)
|
||||
@info("Mass matrix for is already assembled, not assembling.",
|
||||
problem.name)
|
||||
return nothing
|
||||
end
|
||||
elements = get_elements(problem)
|
||||
for (element_type, elements) in group_by_element_type(get_elements(problem))
|
||||
assemble_mass_matrix!(problem::Problem, elements, time)
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
function assemble_mass_matrix!(problem::Problem, elements::Vector{E}, time) where E<:AbstractElement{M_,B} where {M_,B}
|
||||
nnodes = length(first(elements))
|
||||
dim = get_unknown_field_dimension(problem)
|
||||
M = zeros(nnodes, nnodes)
|
||||
N = zeros(1, nnodes)
|
||||
NtN = zeros(nnodes, nnodes)
|
||||
ldofs = zeros(Int, nnodes)
|
||||
for element in elements
|
||||
fill!(M, 0.0)
|
||||
for ip in get_integration_points(element, 2)
|
||||
detJ = element(ip, time, Val{:detJ})
|
||||
rho = element("density", ip, time)
|
||||
w = ip.weight*rho*detJ
|
||||
eval_basis!(B, N, ip)
|
||||
N = element(ip, time)
|
||||
mul!(NtN, transpose(N), N)
|
||||
rmul!(NtN, w)
|
||||
for i=1:nnodes^2
|
||||
M[i] += NtN[i]
|
||||
end
|
||||
end
|
||||
for (i, j) in enumerate(get_connectivity(element))
|
||||
@inbounds ldofs[i] = (j-1)*dim
|
||||
end
|
||||
for i=1:dim
|
||||
add!(problem.assembly.M, ldofs.+i, ldofs.+i, M)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
"""
|
||||
assemble_mass_matrix!(problem, elements::Vector{Element{Tet10}}, time)
|
||||
|
||||
Assemble Tet10 mass matrices using special method. If Tet10 has constant metric
|
||||
if can be integrated analytically to gain performance.
|
||||
"""
|
||||
function assemble_mass_matrix!(problem::Problem, elements::Vector{E}, time) where E<:AbstractElement{M_, Tet10} where M_
|
||||
nnodes = length(Tet10)
|
||||
dim = get_unknown_field_dimension(problem)
|
||||
M = zeros(nnodes, nnodes)
|
||||
N = zeros(1, nnodes)
|
||||
NtN = zeros(nnodes, nnodes)
|
||||
ldofs = zeros(Int, nnodes)
|
||||
|
||||
M_CM = 1.0/2520.0 * [
|
||||
6 1 1 1 -4 -6 -4 -4 -6 -6
|
||||
1 6 1 1 -4 -4 -6 -6 -4 -6
|
||||
1 1 6 1 -6 -4 -4 -6 -6 -4
|
||||
1 1 1 6 -6 -6 -6 -4 -4 -4
|
||||
-4 -4 -6 -6 32 16 16 16 16 8
|
||||
-6 -4 -4 -6 16 32 16 8 16 16
|
||||
-4 -6 -4 -6 16 16 32 16 8 16
|
||||
-4 -6 -6 -4 16 8 16 32 16 16
|
||||
-6 -4 -6 -4 16 16 8 16 32 16
|
||||
-6 -6 -4 -4 8 16 16 16 16 32]
|
||||
|
||||
function is_CM(::AbstractElement{M, Tet10}, X; rtol=1.0e-6) where M
|
||||
isapprox(X[5], 1/2*(X[1]+X[2]); rtol=rtol) || return false
|
||||
isapprox(X[6], 1/2*(X[2]+X[3]); rtol=rtol) || return false
|
||||
isapprox(X[7], 1/2*(X[3]+X[1]); rtol=rtol) || return false
|
||||
isapprox(X[8], 1/2*(X[1]+X[4]); rtol=rtol) || return false
|
||||
isapprox(X[9], 1/2*(X[2]+X[4]); rtol=rtol) || return false
|
||||
isapprox(X[10], 1/2*(X[3]+X[4]); rtol=rtol) || return false
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
n_CM = 0
|
||||
for element in elements
|
||||
for (i, j) in enumerate(get_connectivity(element))
|
||||
@inbounds ldofs[i] = (j-1)*dim
|
||||
end
|
||||
|
||||
X = element("geometry", time)
|
||||
rho = element("density", time)
|
||||
if is_CM(element, X) && length(rho) == 1
|
||||
ip = (1.0/3.0, 1.0/3.0, 1.0/3.0)
|
||||
detJ = element(ip, time, Val{:detJ})
|
||||
rho = element("density", ip, time)
|
||||
CM_s = detJ*rho
|
||||
n_CM += 1
|
||||
for i=1:dim
|
||||
add!(problem.assembly.M, ldofs .+ i, ldofs .+ i, CM_s * M_CM)
|
||||
end
|
||||
else
|
||||
fill!(M, 0.0)
|
||||
for ip in get_integration_points(element, 2)
|
||||
detJ = element(ip, time, Val{:detJ})
|
||||
rho = element("density", ip, time)
|
||||
w = ip.weight*rho*detJ
|
||||
eval_basis!(Tet10, N, ip)
|
||||
N = element(ip, time)
|
||||
mul!(NtN, transpose(N), N)
|
||||
rmul!(NtN, w)
|
||||
for i=1:nnodes^2
|
||||
M[i] += NtN[i]
|
||||
end
|
||||
end
|
||||
for i=1:dim
|
||||
add!(problem.assembly.M, ldofs .+ i, ldofs .+ i, M)
|
||||
end
|
||||
end
|
||||
end
|
||||
@info("$n_CM of $(length(elements)) was constant metric.")
|
||||
return
|
||||
end
|
||||
@@ -0,0 +1,484 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
abstract type AbstractProblem end
|
||||
abstract type FieldProblem<:AbstractProblem end
|
||||
abstract type BoundaryProblem<:AbstractProblem end
|
||||
abstract type MixedProblem<:AbstractProblem end
|
||||
|
||||
"""
|
||||
General linearized problem to solve
|
||||
(K₁+K₂)Δu + C1'*Δλ = f₁+f₂
|
||||
C2Δu + D*Δλ = g
|
||||
"""
|
||||
mutable struct Assembly
|
||||
|
||||
M :: SparseMatrixCOO # mass matrix
|
||||
|
||||
# for field assembly
|
||||
K :: SparseMatrixCOO # stiffness matrix
|
||||
Kg :: SparseMatrixCOO # geometric stiffness matrix
|
||||
f :: SparseMatrixCOO # force vector
|
||||
fg :: SparseMatrixCOO #
|
||||
|
||||
# for boundary assembly
|
||||
C1 :: SparseMatrixCOO
|
||||
C2 :: SparseMatrixCOO
|
||||
D :: SparseMatrixCOO
|
||||
g :: SparseMatrixCOO
|
||||
c :: SparseMatrixCOO
|
||||
|
||||
u :: Vector{Float64} # solution vector u
|
||||
u_prev :: Vector{Float64} # previous solution vector u
|
||||
u_norm_change :: Real # change of norm in u
|
||||
|
||||
la :: Vector{Float64} # solution vector la
|
||||
la_prev :: Vector{Float64} # previous solution vector u
|
||||
la_norm_change :: Real # change of norm in la
|
||||
|
||||
removed_dofs :: Vector{Int} # manually remove dofs from assembly
|
||||
end
|
||||
|
||||
function Assembly()
|
||||
return Assembly(
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
SparseMatrixCOO(),
|
||||
[], [], Inf,
|
||||
[], [], Inf,
|
||||
[])
|
||||
end
|
||||
|
||||
function empty!(assembly::Assembly)
|
||||
empty!(assembly.M)
|
||||
empty!(assembly.K)
|
||||
empty!(assembly.Kg)
|
||||
empty!(assembly.f)
|
||||
empty!(assembly.fg)
|
||||
empty!(assembly.C1)
|
||||
empty!(assembly.C2)
|
||||
empty!(assembly.D)
|
||||
empty!(assembly.g)
|
||||
empty!(assembly.c)
|
||||
end
|
||||
|
||||
function isempty(assembly::Assembly)
|
||||
T = isempty(assembly.M)
|
||||
T &= isempty(assembly.K)
|
||||
T &= isempty(assembly.Kg)
|
||||
T &= isempty(assembly.f)
|
||||
T &= isempty(assembly.fg)
|
||||
T &= isempty(assembly.C1)
|
||||
T &= isempty(assembly.C2)
|
||||
T &= isempty(assembly.D)
|
||||
T &= isempty(assembly.g)
|
||||
T &= isempty(assembly.c)
|
||||
return T
|
||||
end
|
||||
|
||||
"""
|
||||
Problem{P<:AbstractProblem}
|
||||
|
||||
Defines a new problem of type `P`, where `P` characterizes the physics of the
|
||||
problem. `P` can be for example `Elasticity`, if the physics of the system is
|
||||
described by Cauchy's stress equation ∇⋅σ + b = ̈ρu, or `Heat`, if the physics
|
||||
of the problem is described by heat equation -∇⋅(k∇u) = f.
|
||||
|
||||
"""
|
||||
mutable struct Problem{P<:AbstractProblem}
|
||||
name :: AbstractString # descriptive name for the problem
|
||||
dimension :: Int # degrees of freedom per node
|
||||
parent_field_name :: AbstractString # (optional) name of the parent field e.g. "displacement"
|
||||
elements :: Vector{Element}
|
||||
dofmap :: Dict{Element, Vector{Int}} # connects the element local dofs to the global dofs
|
||||
assembly :: Assembly
|
||||
fields :: Dict{String, AbstractField}
|
||||
postprocess_fields :: Vector{String}
|
||||
properties :: P
|
||||
end
|
||||
|
||||
"""
|
||||
Problem(problem_type, problem_name, problem_dimension)
|
||||
|
||||
Construct a new field problem.
|
||||
|
||||
`problem_type` must be a subtype of `FieldProblem` (`Elasticity`, `Heat`, etc..).
|
||||
`problem_dimensions` is the number of degrees of freedom each node is containing.
|
||||
|
||||
# Examples
|
||||
|
||||
To create vector-valued elasticity problem, having 3 dofs / node:
|
||||
```julia
|
||||
problem1 = Problem(Elasticity, "test problem", 3)
|
||||
```
|
||||
|
||||
To create scalar-valued Poisson problem:
|
||||
```julia
|
||||
problem2 = Problem(Heat, "test problem 2", 1)
|
||||
```
|
||||
|
||||
"""
|
||||
function Problem(::Type{P}, name::AbstractString, dimension::Int) where P<:FieldProblem
|
||||
parent_field_name = "none"
|
||||
elements = []
|
||||
dofmap = Dict()
|
||||
assembly = Assembly()
|
||||
fields = Dict()
|
||||
postprocess_fields = Vector()
|
||||
properties = P()
|
||||
problem = Problem{P}(name, dimension, parent_field_name, elements, dofmap,
|
||||
assembly, fields, postprocess_fields, properties)
|
||||
@info("Creating a new problem of type $P, having name `$name` and " *
|
||||
"dimension $dimension dofs/node.")
|
||||
return problem
|
||||
end
|
||||
|
||||
"""
|
||||
Problem(problem_type, problem_name, problem_dimension, parent_field_name)
|
||||
|
||||
Construct a new boundary problem.
|
||||
|
||||
`problem_type` must be a subtype of `BoundaryProblem` (`Dirichlet`, `Contact`,
|
||||
etc..). `problem_dimensions` is the number of degrees of freedom each node is
|
||||
containing. `parent_field_name` is describing the field, where the boundary
|
||||
problem is affecting.
|
||||
|
||||
# Examples
|
||||
|
||||
To create a Dirichlet boundary condition for a vector-valued elasticity problem,
|
||||
having 3 dofs / node:
|
||||
```julia
|
||||
bc1 = Problem(Dirichlet, "fix displacement on support", 3, "displacement")
|
||||
```
|
||||
|
||||
To create a Dirichlet boundary condition for scalar-valued Poisson problem:
|
||||
```julia
|
||||
bc2 = Problem(Dirichlet, "fix surface temperature", 1, "temperature")
|
||||
```
|
||||
"""
|
||||
function Problem(::Type{P}, name, dimension, parent_field_name) where P<:BoundaryProblem
|
||||
elements = []
|
||||
dofmap = Dict()
|
||||
assembly = Assembly()
|
||||
fields = Dict()
|
||||
postprocess_fields = Vector()
|
||||
properties = P()
|
||||
problem = Problem{P}(name, dimension, parent_field_name, elements, dofmap,
|
||||
assembly, fields, postprocess_fields, properties)
|
||||
@info("Creating a new boundary problem of type $P, having name `$name` and " *
|
||||
"dimension $dimension dofs/node. This boundary problems fixes field " *
|
||||
"`$parent_field_name`.")
|
||||
return problem
|
||||
end
|
||||
|
||||
function get_formulation_type(::Problem)
|
||||
return :incremental
|
||||
end
|
||||
|
||||
"""
|
||||
get_unknown_field_dimension(problem)
|
||||
|
||||
Return the dimension of the unknown field of this problem.
|
||||
"""
|
||||
function get_unknown_field_dimension(problem::Problem)
|
||||
return problem.dimension
|
||||
end
|
||||
|
||||
"""
|
||||
get_unknown_field_name(problem)
|
||||
|
||||
Default function if unknown field name is not defined for some problem.
|
||||
"""
|
||||
function get_unknown_field_name(::P) where P<:AbstractProblem
|
||||
@warn("The name of unknown field (e.g. displacement, temperature, ...) of the " *
|
||||
"problem type must be given by defining a function " *
|
||||
"`get_unknown_field_name(::$P)`")
|
||||
return "N/A"
|
||||
end
|
||||
|
||||
""" Return the name of the unknown field of this problem. """
|
||||
function get_unknown_field_name(problem::Problem{P}) where P
|
||||
return get_unknown_field_name(problem.properties)
|
||||
end
|
||||
|
||||
""" Return the name of the parent field of this (boundary) problem. """
|
||||
function get_parent_field_name(problem::Problem{P}) where P<:BoundaryProblem
|
||||
return problem.parent_field_name
|
||||
end
|
||||
|
||||
function get_unknown_field_name(::P) where P<:BoundaryProblem
|
||||
return "lambda"
|
||||
end
|
||||
|
||||
is_field_problem(::Problem) = false
|
||||
is_field_problem(::Problem{P}) where {P<:FieldProblem} = true
|
||||
is_boundary_problem(::Problem) = false
|
||||
is_boundary_problem(::Problem{P}) where {P<:BoundaryProblem} = true
|
||||
|
||||
function get_elements(problem::Problem)
|
||||
return problem.elements
|
||||
end
|
||||
|
||||
function update!(problem::P, attr::Pair{String, String}...) where P<:AbstractProblem
|
||||
for (name, value) in attr
|
||||
setfield!(problem, Meta.parse(name), Meta.parse(value))
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
function initialize!(problem_type, element_name, time)
|
||||
|
||||
Initialize the element ready for calculation, where `problem_type` is the type
|
||||
of the problem (Elasticity, Dirichlet, etc.), `element_name` is the name of a
|
||||
constructed element (see Element(element_type, connectivity_vector)) and `time`
|
||||
is the starting time of the initializing process.
|
||||
"""
|
||||
function initialize!(problem::Problem, element::AbstractElement, time::Float64)
|
||||
field_name = get_unknown_field_name(problem)
|
||||
field_dim = get_unknown_field_dimension(problem)
|
||||
nnodes = length(element)
|
||||
if field_dim == 1 # scalar field
|
||||
empty_field = tuple(zeros(nnodes)...)
|
||||
else # vector field
|
||||
# FIXME: the most effective way to do
|
||||
# ([0.0,0.0], [0.0,0.0], ..., [0.0,0.0]) ?
|
||||
empty_field = tuple(map((x)->zeros(field_dim)*x, 1:nnodes)...)
|
||||
end
|
||||
|
||||
# initialize primary field
|
||||
if !haskey(element, field_name)
|
||||
update!(element, field_name, time => empty_field)
|
||||
end
|
||||
|
||||
# if a boundary problem, initialize also a field for the main problem
|
||||
is_boundary_problem(problem) || return
|
||||
field_name = get_parent_field_name(problem)
|
||||
if !haskey(element, field_name)
|
||||
update!(element, field_name, time => empty_field)
|
||||
end
|
||||
end
|
||||
|
||||
function initialize!(problem::Problem, time::Float64=0.0)
|
||||
for element in get_elements(problem)
|
||||
initialize!(problem, element, time)
|
||||
end
|
||||
end
|
||||
|
||||
function update!(problem::Problem, assembly::Assembly, u::Vector, la::Vector)
|
||||
|
||||
# resize & fill with zeros vectors if length mismatch with current solution
|
||||
|
||||
if length(u) != length(assembly.u)
|
||||
resize!(assembly.u, length(u))
|
||||
fill!(assembly.u, 0.0)
|
||||
end
|
||||
|
||||
if length(la) != length(assembly.la)
|
||||
resize!(assembly.la, length(la))
|
||||
fill!(assembly.la, 0.0)
|
||||
end
|
||||
|
||||
# copy current solutions to previous ones and add/replace new solution
|
||||
# TODO: here we have couple of options and they need to be clarified
|
||||
# for total formulation we are solving total quantity Ku = f while in
|
||||
# incremental formulation we solve KΔu = f and u = u + Δu
|
||||
assembly.u_prev = copy(assembly.u)
|
||||
assembly.la_prev = copy(assembly.la)
|
||||
|
||||
if get_formulation_type(problem) == :total
|
||||
assembly.u = u
|
||||
assembly.la = la
|
||||
elseif get_formulation_type(problem) == :incremental
|
||||
assembly.u += u
|
||||
assembly.la = la
|
||||
elseif get_formulation_type(problem) == :forwarddiff
|
||||
assembly.u += u
|
||||
assembly.la += la
|
||||
else
|
||||
@info("$(problem.name): unknown formulation type, don't know what to do with results")
|
||||
error("serious failure with problem formulation: $(get_formulation_type(problem))")
|
||||
end
|
||||
|
||||
# calculate change of norm
|
||||
assembly.u_norm_change = norm(assembly.u - assembly.u_prev)
|
||||
assembly.la_norm_change = norm(assembly.la - assembly.la_prev)
|
||||
return assembly.u, assembly.la
|
||||
end
|
||||
|
||||
"""
|
||||
get_global_solution(problem, assembly)
|
||||
|
||||
Return a global solution (u, la) for a problem.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If the length of solution vector != number of nodes, i.e. the field dimension is
|
||||
something else than 1, reshape vectors so that their length matches to the
|
||||
number of nodes. This helps to get nodal results easily.
|
||||
"""
|
||||
function get_global_solution(problem::Problem, assembly::Assembly)
|
||||
u = assembly.u
|
||||
la = assembly.la
|
||||
field_dim = get_unknown_field_dimension(problem)
|
||||
if field_dim == 1
|
||||
return u, la
|
||||
else
|
||||
nnodes = round(Int, length(u)/field_dim)
|
||||
u = reshape(u, field_dim, nnodes)
|
||||
u = Vector{Float64}[u[:,i] for i in 1:nnodes]
|
||||
la = reshape(la, field_dim, nnodes)
|
||||
la = Vector{Float64}[la[:,i] for i in 1:nnodes]
|
||||
return u, la
|
||||
end
|
||||
end
|
||||
|
||||
function update!(problem::Problem{P}, assembly::Assembly, elements::Vector{Element}, time::Float64) where P<:FieldProblem
|
||||
u, la = get_global_solution(problem, assembly)
|
||||
field_name = get_unknown_field_name(problem)
|
||||
# update solution u for elements
|
||||
for element in elements
|
||||
connectivity = get_connectivity(element)
|
||||
update!(element, field_name, time => tuple(u[connectivity]...))
|
||||
end
|
||||
end
|
||||
|
||||
function update!(problem::Problem{P}, assembly::Assembly, elements::Vector{Element}, time::Float64) where P<:BoundaryProblem
|
||||
u, la = get_global_solution(problem, assembly)
|
||||
parent_field_name = get_parent_field_name(problem) # displacement
|
||||
field_name = get_unknown_field_name(problem) # lambda
|
||||
# update solution and lagrange multipliers for boundary elements
|
||||
for element in elements
|
||||
connectivity = get_connectivity(element)
|
||||
update!(element, parent_field_name, time => tuple(u[connectivity]...))
|
||||
update!(element, field_name, time => tuple(la[connectivity]...))
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
add_element!(problem, element1, element2, ...)
|
||||
|
||||
Add element(s) to the problem.
|
||||
"""
|
||||
function add_element!(problem, elements...)
|
||||
for element in elements
|
||||
push!(problem.elements, element)
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
"""
|
||||
add_elements!(problem, element_set_1, element_set_2, ...)
|
||||
|
||||
Add vectors/tuples of element(s) to the problem.
|
||||
"""
|
||||
function add_elements!(problem, element_sets::Union{Vector,Tuple}...)
|
||||
for elements in element_sets
|
||||
nelements = length(elements)
|
||||
@info("Adding $nelements elements to problem `$(problem.name)`")
|
||||
add_element!(problem, elements...)
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
add_elements!(problem, elements::Element...) = add_element!(problem, elements...)
|
||||
|
||||
function add_elements!(problem, elements_or_lists_of_elements...)
|
||||
for item in elements_or_lists_of_elements
|
||||
add_elements!(problem, item)
|
||||
end
|
||||
end
|
||||
|
||||
get_assembly(problem::Problem) = problem.assembly
|
||||
Base.length(problem::Problem) = length(problem.elements)
|
||||
|
||||
function update!(problem::Problem, field_name::AbstractString, data)
|
||||
#if haskey(problem.fields, field_name)
|
||||
# update!(problem.fields[field_name], field_name::AbstractString, data)
|
||||
#else
|
||||
# problem.fields[field_name] = Field(data)
|
||||
#end
|
||||
update!(problem.elements, field_name::AbstractString, data)
|
||||
end
|
||||
|
||||
function haskey(problem::Problem, field_name::AbstractString)
|
||||
return haskey(problem.fields, field_name)
|
||||
end
|
||||
|
||||
function getindex(problem::Problem, field_name::String)
|
||||
return problem.fields[field_name]
|
||||
end
|
||||
|
||||
#""" Return field calculated to nodal points for elements in problem p. """
|
||||
function (problem::Problem)(field_name::String, time::Float64)
|
||||
#if haskey(problem, field_name)
|
||||
# return problem[field_name](time)
|
||||
#end
|
||||
f = Dict{Int, Any}()
|
||||
for element in get_elements(problem)
|
||||
haskey(element, field_name) || continue
|
||||
for (c, v) in zip(get_connectivity(element), element(field_name, time))
|
||||
if haskey(f, c)
|
||||
if !isapprox(f[c], v)
|
||||
@info("several values for single node when returning field $field_name")
|
||||
@info("already have: $(f[c]), and trying to set $v")
|
||||
end
|
||||
else
|
||||
f[c] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
#f == nothing && return f
|
||||
#update!(problem, field_name, time => f)
|
||||
return f
|
||||
end
|
||||
|
||||
function push!(problem::Problem, elements...)
|
||||
push!(problem.elements, elements...)
|
||||
end
|
||||
|
||||
function push!(problem::Problem, elements_::Vector...)
|
||||
for elements in elements_
|
||||
push!(problem.elements, elements...)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
set_gdofs!(problem, element)
|
||||
|
||||
Set element global degrees of freedom.
|
||||
"""
|
||||
function set_gdofs!(problem, element, dofs)
|
||||
problem.dofmap[element] = dofs
|
||||
end
|
||||
|
||||
"""
|
||||
get_gdofs(problem, element)
|
||||
|
||||
Return the global degrees of freedom for element.
|
||||
|
||||
First make lookup from problem dofmap. If not defined there, make implicit
|
||||
assumption that dofs follow formula `gdofs = [dim*(nid-1)+j for j=1:dim]`,
|
||||
where `nid` is node id and `dim` is the dimension of problem. This formula
|
||||
arranges dofs so that first comes all dofs of node 1, then node 2 and so on:
|
||||
(u11, u12, u13, u21, u22, u23, ..., un1, un2, un3) for 3 dofs/node setting.
|
||||
"""
|
||||
function get_gdofs(problem::Problem, element::AbstractElement)
|
||||
if haskey(problem.dofmap, element)
|
||||
return problem.dofmap[element]
|
||||
end
|
||||
conn = get_connectivity(element)
|
||||
if length(conn) == 0
|
||||
error("element connectivity not defined, cannot determine global ",
|
||||
"degrees of freedom for element #: $(element.id)")
|
||||
end
|
||||
dim = get_unknown_field_dimension(problem)
|
||||
gdofs = [dim*(i-1)+j for i in conn for j=1:dim]
|
||||
return gdofs
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE
|
||||
|
||||
# AbstractBasis type and interface
|
||||
# Consolidated from FEMBasis.jl package
|
||||
# Consolidated from jl package
|
||||
|
||||
using Tensors
|
||||
using LinearAlgebra
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
__precompile__(false)
|
||||
|
||||
@@ -22,7 +22,7 @@ function calculate_interpolation_polynomials(p, V)
|
||||
N = Expr(:call, :+)
|
||||
for (ai, bi) in zip(solution, args)
|
||||
isapprox(ai, 0.0) && continue
|
||||
push!(N.args, Calculus.simplify( :( $ai * $bi ) ))
|
||||
push!(N.args, Calculus.simplify(:($ai * $bi)))
|
||||
end
|
||||
push!(basis, N)
|
||||
end
|
||||
@@ -55,17 +55,17 @@ function create_basis(name, description, X::Vector{<:Vecish{D}}, basis::Vector)
|
||||
return create_basis(name, description, Vec.(X), basis, dbasis)
|
||||
end
|
||||
|
||||
function create_basis(name, description, X::Vector{<:Vecish{D, T}}, basis, dbasis) where {D, T}
|
||||
function create_basis(name, description, X::Vector{<:Vecish{D,T}}, basis, dbasis) where {D,T}
|
||||
N = length(X)
|
||||
@debug "create basis given basis functions and derivatives" name description X basis dbasis
|
||||
|
||||
Q = Expr(:block)
|
||||
for i=1:N
|
||||
for i = 1:N
|
||||
push!(Q.args, :(N[$i] = $(basis[i])))
|
||||
end
|
||||
|
||||
V = Expr(:block)
|
||||
for i=1:N
|
||||
for i = 1:N
|
||||
push!(V.args, :(dN[$i] = Vec(float.(tuple($(dbasis[:, i]...))))))
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
code = create_basis_and_eval(
|
||||
:Hex8,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
# Kaltenbacher, Manfred. Numerical simulation of mechatronic sensors and actuators: finite elements for computational multiphysics. Springer, 2015.
|
||||
code = create_basis_and_eval(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
code = create_basis_and_eval(
|
||||
:Quad4,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
code = create_basis_and_eval(
|
||||
:Seg2,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
code = create_basis_and_eval(
|
||||
:Tet4,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
code = create_basis_and_eval(
|
||||
:Tri3,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
# Kaltenbacher, Manfred. Numerical simulation of mechatronic sensors and actuators: finite elements for computational multiphysics. Springer, 2015.
|
||||
create_basis_and_eval(
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
"""
|
||||
interpolate(B, T, xi)
|
||||
@@ -148,7 +148,7 @@ BasisInfo(Tri3)
|
||||
|
||||
# output
|
||||
|
||||
FEMBasis.BasisInfo{FEMBasis.Tri3,Float64}([0.0 0.0 0.0], [0.0 0.0 0.0; 0.0 0.0 0.0], [0.0 0.0 0.0; 0.0 0.0 0.0], [0.0 0.0; 0.0 0.0], [0.0 0.0; 0.0 0.0], 0.0)
|
||||
BasisInfo{Tri3,Float64}([0.0 0.0 0.0], [0.0 0.0 0.0; 0.0 0.0 0.0], [0.0 0.0 0.0; 0.0 0.0 0.0], [0.0 0.0; 0.0 0.0], [0.0 0.0; 0.0 0.0], 0.0)
|
||||
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
import Base: size, length
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
""" NURBS segment. """
|
||||
mutable struct NSeg <: AbstractBasis{1}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
mutable struct NSolid <: AbstractBasis{3}
|
||||
order_u :: Int
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
mutable struct NSurf <: AbstractBasis{2}
|
||||
order_u :: Int
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
function subs(p::Number, ::Any)
|
||||
return p
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBasis.jl/blob/master/LICENSE
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
"""
|
||||
vandermonde_matrix(polynomial, coordinates)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
const Node = Vector{Float64}
|
||||
|
||||
abstract type AbstractPoint end
|
||||
|
||||
mutable struct Point{P<:AbstractPoint}
|
||||
id :: Int
|
||||
weight :: Float64
|
||||
coords :: Tuple{Vararg{Float64}}
|
||||
fields :: Dict{String, AbstractField}
|
||||
properties :: P
|
||||
end
|
||||
|
||||
function setindex!(point::Point, val::Pair{Float64, T}, field_name) where T
|
||||
point.fields[field_name] = field(val)
|
||||
end
|
||||
|
||||
function getindex(point::Point, field_name)
|
||||
return point.fields[field_name]
|
||||
end
|
||||
|
||||
function getindex(point::Point, idx::Int)
|
||||
return point.coords[idx]
|
||||
end
|
||||
|
||||
function haskey(point::Point, field_name)
|
||||
return haskey(point.fields, field_name)
|
||||
end
|
||||
|
||||
function (point::Point)(field_name, time)
|
||||
interpolate(point.fields[field_name], time)
|
||||
end
|
||||
|
||||
function Base.iterate(point::Point)
|
||||
return Base.iterate(point.coords)
|
||||
end
|
||||
|
||||
function Base.iterate(point::Point, i::Int)
|
||||
return Base.iterate(point.coords, i)
|
||||
end
|
||||
|
||||
function update!(point::Point, field_name, val::Pair{Float64, T}) where T
|
||||
if haskey(point, field_name)
|
||||
update!(point[field_name], val)
|
||||
else
|
||||
point[field_name] = val
|
||||
end
|
||||
end
|
||||
|
||||
#= TODO: in future
|
||||
type Node <: AbstractPoint
|
||||
end
|
||||
|
||||
type MaterialPoint <: AbstractPoint
|
||||
end
|
||||
=#
|
||||
|
||||
struct IntegrationPoint <: AbstractPoint
|
||||
end
|
||||
|
||||
const IP = Point{IntegrationPoint}
|
||||
|
||||
function IP(id, weight, coords::Tuple)
|
||||
return IP(id, weight, coords, Dict(), IntegrationPoint())
|
||||
end
|
||||
|
||||
function IP(id, weight, coords::Vector)
|
||||
@warn "Consider giving coordinates as tuple."
|
||||
return IP(id, weight, tuple(coords...), Dict(), IntegrationPoint())
|
||||
end
|
||||
@@ -0,0 +1,527 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
"""
|
||||
AbstractFieldSet{N<:Int}
|
||||
|
||||
Abstract supertype for all field sets, where `N` is the length of the discrete
|
||||
fields (typically is the number of the nodes in element).
|
||||
"""
|
||||
abstract type AbstractFieldSet{N} end
|
||||
|
||||
"""
|
||||
EmptyFieldSet{N} <: AbstractFieldSet{N}
|
||||
|
||||
Empty field set used as a default for all elements.
|
||||
"""
|
||||
struct EmptyFieldSet{N} <: AbstractFieldSet{N}
|
||||
end
|
||||
|
||||
const DefaultFieldSet = EmptyFieldSet
|
||||
|
||||
"""
|
||||
AbstractElement{M<:AbstractFieldSet, B<:AbstractBasis}
|
||||
|
||||
Abstract supertype for all elements.
|
||||
"""
|
||||
abstract type AbstractElement{M<:AbstractFieldSet, B<:AbstractBasis} end
|
||||
|
||||
mutable struct Element{M,B} <: AbstractElement{M,B}
|
||||
id :: Int
|
||||
connectivity :: Vector{Int}
|
||||
integration_points :: Vector{IP}
|
||||
dfields :: Dict{Symbol, AbstractField}
|
||||
sfields :: M
|
||||
properties :: B
|
||||
end
|
||||
|
||||
"""
|
||||
Element(topology, connectivity)
|
||||
|
||||
Construct a new element where `topology` is the topological type of the element
|
||||
and connectivity contains node numbers where element is connected.
|
||||
|
||||
# Topological types
|
||||
|
||||
## 1d elements
|
||||
- `Seg2`
|
||||
- `Seg3`
|
||||
|
||||
## 2d elements
|
||||
- `Tri3`
|
||||
- `Tri6`
|
||||
- `Tri7`
|
||||
- `Quad4`
|
||||
- `Quad8`
|
||||
- `Quad9`
|
||||
|
||||
## 3d elements
|
||||
- `Tet4`
|
||||
- `Tet10`
|
||||
- `Hex8`
|
||||
- `Hex20`
|
||||
- `Hex27`
|
||||
- `Pyr5`
|
||||
- `Wedge6`
|
||||
- `Wedge15`
|
||||
|
||||
# Examples
|
||||
|
||||
```julia
|
||||
element = Element(Tri3, (1, 2, 3))
|
||||
```
|
||||
"""
|
||||
function Element(::Type{T}, connectivity::NTuple{N, Int}) where {N, T<:AbstractBasis}
|
||||
return Element(T, DefaultFieldSet, connectivity)
|
||||
end
|
||||
|
||||
function Element(::Type{T}, ::Type{M}, connectivity::NTuple{N, Int}) where {N, M<:AbstractFieldSet, T<:AbstractBasis}
|
||||
element_id = -1
|
||||
topology = T()
|
||||
integration_points = Point{IntegrationPoint}[]
|
||||
dfields = Dict{Symbol,AbstractField}()
|
||||
sfields = M{N}()
|
||||
element = Element(element_id, collect(connectivity), integration_points,
|
||||
dfields, sfields, topology)
|
||||
return element
|
||||
end
|
||||
|
||||
function Element(::Type{T}, connectivity::Vector{Int}) where T<:AbstractBasis
|
||||
return Element(T, (connectivity...,))
|
||||
end
|
||||
|
||||
function get_element_id(element::AbstractElement)
|
||||
return element.id
|
||||
end
|
||||
|
||||
function get_element_type(::AbstractElement{M,T}) where {M,T}
|
||||
return T
|
||||
end
|
||||
|
||||
function is_element_type(::AbstractElement{M,T}, element_type) where {M,T}
|
||||
return T === element_type
|
||||
end
|
||||
|
||||
function filter_by_element_type(element_type, elements)
|
||||
return Iterators.filter(element -> is_element_type(element, element_type), elements)
|
||||
end
|
||||
|
||||
function get_connectivity(element::AbstractElement)
|
||||
return element.connectivity
|
||||
end
|
||||
|
||||
"""
|
||||
group_by_element_type(elements)
|
||||
|
||||
Given a vector of elements, group elements by element type to several vectors.
|
||||
Returns a dictionary, where key is the element type and value is a vector
|
||||
containing all elements of type `element_type`.
|
||||
"""
|
||||
function group_by_element_type(elements)
|
||||
eltypes = map(T -> typeof(T), elements)
|
||||
elgroups = Dict(T => T[] for T in eltypes)
|
||||
for element in elements
|
||||
T = typeof(element)
|
||||
push!(elgroups[T], element)
|
||||
end
|
||||
return elgroups
|
||||
end
|
||||
|
||||
### dfields - dynamically defined fields
|
||||
|
||||
# This is the "old" field system, where fields are defined to dictionary.
|
||||
# It is known that this approach is having a performance issue caused by
|
||||
# type instability.
|
||||
|
||||
function has_dfield(element, field_name)
|
||||
return haskey(element.dfields, field_name)
|
||||
end
|
||||
|
||||
function get_dfield(element, field_name)
|
||||
return getindex(element.dfields, field_name)
|
||||
end
|
||||
|
||||
function create_dfield!(element, field_name, field_::AbstractField)
|
||||
T = typeof(field_)
|
||||
if has_dfield(element, field_name)
|
||||
@debug("Replacing the content of a field $field_name with a new field of type $T.")
|
||||
else
|
||||
@debug("Creating a new dfield $field_name of type $T")
|
||||
end
|
||||
element.dfields[field_name] = field_
|
||||
return
|
||||
end
|
||||
|
||||
function create_dfield!(element, field_name, field_data)
|
||||
create_dfield!(element, field_name, field(field_data))
|
||||
end
|
||||
|
||||
function update_dfield!(element, field_name, field_data)
|
||||
if has_dfield(element, field_name)
|
||||
field = get_dfield(element, field_name)
|
||||
@debug("Update $field_name with data $field_data")
|
||||
update_field!(field, field_data)
|
||||
else
|
||||
create_dfield!(element, field_name, field_data)
|
||||
end
|
||||
end
|
||||
|
||||
# A helper function to pick element data from dictionary
|
||||
function pick_data_(element, field_data)
|
||||
connectivity = get_connectivity(element)
|
||||
N = length(connectivity)
|
||||
picked_data = ntuple(i -> getindex(field_data, connectivity[i]), N)
|
||||
return picked_data
|
||||
end
|
||||
|
||||
function update_dfield!(element, field_name, (time, field_data)::Pair{Float64, Dict{Int,V}}) where V
|
||||
update_dfield!(element, field_name, time => pick_data_(element, field_data))
|
||||
end
|
||||
|
||||
function update_dfield!(element, field_name, field_data::Dict{Int,V}) where V
|
||||
update_dfield!(element, field_name, pick_data_(element, field_data))
|
||||
end
|
||||
|
||||
function update_dfield!(element, field_name, field_data::Function)
|
||||
if hasmethod(field_data, Tuple{Element, Any, Any})
|
||||
element.dfields[field_name] = field((ip, time) -> field_data(element, ip, time))
|
||||
else
|
||||
element.dfields[field_name] = field(field_data)
|
||||
end
|
||||
end
|
||||
|
||||
function interpolate_dfield(element, field_name, time)
|
||||
field = get_dfield(element, field_name)
|
||||
return interpolate(field, time)
|
||||
end
|
||||
|
||||
### sfields statically defined fields
|
||||
|
||||
# A new-style field system, where fields are defined in sfields <: AbstractFieldSet
|
||||
# during the initialization of element.
|
||||
|
||||
function has_sfield(element, field_name)
|
||||
return isdefined(element.sfields, field_name)
|
||||
end
|
||||
|
||||
function get_sfield(element, field_name)
|
||||
return getfield(element.sfields, field_name)
|
||||
end
|
||||
|
||||
function update_sfield!(element, field_name, field_data)
|
||||
field = get_sfield(element, field_name)
|
||||
update!(field, field_data)
|
||||
end
|
||||
|
||||
function interpolate_sfield(element, field_name, time)
|
||||
field = get_sfield(element, field_name)
|
||||
return interpolate(field, time)
|
||||
end
|
||||
|
||||
### dfield & sfield -- common routines
|
||||
|
||||
function has_field(element, field_name)
|
||||
return has_sfield(element, field_name) || has_dfield(element, field_name)
|
||||
end
|
||||
|
||||
function get_field(element, field_name)
|
||||
if has_sfield(element, field_name)
|
||||
return get_sfield(element, field_name)
|
||||
else
|
||||
return get_dfield(element, field_name)
|
||||
end
|
||||
end
|
||||
|
||||
function update_field!(element, field_name, field_data)
|
||||
if has_sfield(element, field_name)
|
||||
update_sfield!(element, field_name, field_data)
|
||||
else
|
||||
update_dfield!(element, field_name, field_data)
|
||||
end
|
||||
end
|
||||
|
||||
function interpolate_field(element, field_name::Symbol, time)
|
||||
if has_sfield(element, field_name)
|
||||
return interpolate_sfield(element, field_name, time)
|
||||
elseif has_dfield(element, field_name)
|
||||
return interpolate_dfield(element, field_name, time)
|
||||
else
|
||||
error("Cannot interpolate from field $field_name: no such field.")
|
||||
end
|
||||
end
|
||||
|
||||
function interpolate(element::AbstractElement, field_name, time)
|
||||
return interpolate_field(element, field_name, time)
|
||||
end
|
||||
|
||||
function update_field!(elements::Vector{Element}, field_name, field_data)
|
||||
for element in elements
|
||||
update_field!(element, field_name, field_data)
|
||||
end
|
||||
end
|
||||
|
||||
# Update fields when given a dictionary or time => dictionary:
|
||||
# pick data from dictionary diven by the connectivity information of element
|
||||
#=
|
||||
function update_field!(element::AbstractElement, field::F,
|
||||
data::Dict{T,V}) where {F<:DVTI,T,V}
|
||||
connectivity = get_connectivity(element)
|
||||
N = length(connectivity)
|
||||
picked_data = ntuple(i -> data[connectivity[i]], N)
|
||||
update_field!(field, picked_data)
|
||||
end
|
||||
|
||||
function update_field!(element::AbstractElement, field::F,
|
||||
ddata::Pair{Float64, Dict{T,V}}) where {F<:DVTV,T,V}
|
||||
time, data = ddata
|
||||
connectivity = get_connectivity(element)
|
||||
N = length(connectivity)
|
||||
picked_data = ntuple(i -> data[connectivity[i]], N)
|
||||
update_field!(field, time => picked_data)
|
||||
end
|
||||
=#
|
||||
|
||||
"""
|
||||
interpolate(element, field_name, time)
|
||||
|
||||
Interpolate field `field_name` from element at given `time`.
|
||||
|
||||
# Example
|
||||
```
|
||||
element = Element(Seg2, [1, 2])
|
||||
data1 = Dict(1 => 1.0, 2 => 2.0)
|
||||
data2 = Dict(1 => 2.0, 2 => 3.0)
|
||||
update!(element, "my field", 0.0 => data1)
|
||||
update!(element, "my field", 1.0 => data2)
|
||||
interpolate(element, "my field", 0.5)
|
||||
|
||||
# output
|
||||
|
||||
(1.5, 2.5)
|
||||
|
||||
```
|
||||
"""
|
||||
function interpolate(element::AbstractElement, field_name::String, time::Float64)
|
||||
field = element[field_name]
|
||||
result = interpolate(field, time)
|
||||
if isa(result, Dict)
|
||||
connectivity = get_connectivity(element)
|
||||
return tuple((result[i] for i in connectivity)...)
|
||||
else
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
function info_update_field(elements, field_name, data)
|
||||
nelements = length(elements)
|
||||
@info("Updating field `$field_name` for $nelements elements.")
|
||||
end
|
||||
|
||||
function info_update_field(elements, field_name, data::Float64)
|
||||
nelements = length(elements)
|
||||
@info("Updating field `$field_name` => $data for $nelements elements.")
|
||||
end
|
||||
|
||||
"""
|
||||
update!(elements, field_name, data)
|
||||
|
||||
Given a list of elements, field name and data, update field to elements. Data
|
||||
is passed directly to the `field`-function.
|
||||
|
||||
# Examples
|
||||
|
||||
Create two elements with topology `Seg2`, one is connecting to nodes (1, 2) and
|
||||
the other is connecting to (2, 3). Some examples of updating fields:
|
||||
|
||||
```julia
|
||||
elements = [Element(Seg2, [1, 2]), Element(Seg2, [2, 3])]
|
||||
X = Dict(1 => 0.0, 2 => 1.0, 3 => 2.0)
|
||||
u = Dict(1 => 0.0, 2 => 0.0, 3 => 0.0)
|
||||
update!(elements, "geometry", X)
|
||||
update!(elements, "displacement", 0.0 => u)
|
||||
update!(elements, "youngs modulus", 210.0e9)
|
||||
update!(elements, "time-dependent force", 0.0 => 0.0)
|
||||
update!(elements, "time-dependent force", 1.0 => 100.0)
|
||||
```
|
||||
|
||||
When using dictionaries in definition of fields, key of dictionary corresponds
|
||||
to node id, that is, updating field `geometry` in the example above is updating
|
||||
values `(0.0, 1.0)` for the first elements and values `(1.0, 2.0)` to the second
|
||||
element. For time dependent field, syntax `time => data` is used. If field is
|
||||
initialized without time-dependency, it cannot be changed to be time-dependent
|
||||
afterwards. If unsure, it's better to initialize field with time dependency.
|
||||
|
||||
"""
|
||||
function update!(elements, field_name, data)
|
||||
info_update_field(elements, field_name, data)
|
||||
for element in elements
|
||||
update!(element, field_name, data)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
## Interpolate fields in spatial direction
|
||||
|
||||
const ConstantField = Union{DCTI, DCTV}
|
||||
const VariableFields = Union{DVTV, DVTI}
|
||||
const DictionaryFields = Union{DVTVd, DVTId}
|
||||
|
||||
function interpolate_field(::AbstractElement, field::ConstantField, ip, time)
|
||||
return interpolate_field(field, time)
|
||||
end
|
||||
|
||||
function interpolate_field(element::AbstractElement, field::VariableFields, ip, time)
|
||||
data = interpolate_field(field, time)
|
||||
basis = get_basis(element, ip, time)
|
||||
N = length(basis)
|
||||
return sum(data[i]*basis[i] for i=1:N)
|
||||
end
|
||||
|
||||
function interpolate_field(element::AbstractElement, field::DictionaryFields, ip, time)
|
||||
data = interpolate_field(field, time)
|
||||
basis = element(ip, time)
|
||||
N = length(element)
|
||||
c = get_connectivity(element)
|
||||
return sum(data[c[i]]*basis[i] for i=1:N)
|
||||
end
|
||||
|
||||
function interpolate_field(::AbstractElement, field::CVTV, ip, time)
|
||||
return field(ip, time)
|
||||
end
|
||||
|
||||
function interpolate(element::AbstractElement, field_name, ip, time)
|
||||
field = get_field(element, field_name)
|
||||
interpolate_field(element, field, ip, time)
|
||||
end
|
||||
|
||||
|
||||
## Other stuff
|
||||
|
||||
function get_basis(element::AbstractElement{M,B}, ip, ::Any) where {M,B}
|
||||
T = typeof(first(ip))
|
||||
N = zeros(T, 1, length(element))
|
||||
eval_basis!(B, N, tuple(ip...))
|
||||
return N
|
||||
end
|
||||
|
||||
function get_dbasis(element::AbstractElement{M,B}, ip, ::Any) where {M,B}
|
||||
T = typeof(first(ip))
|
||||
dN = zeros(T, size(element)...)
|
||||
eval_dbasis!(B, dN, tuple(ip...))
|
||||
return dN
|
||||
end
|
||||
|
||||
function (element::Element)(ip, time::Float64=0.0)
|
||||
return get_basis(element, ip, time)
|
||||
end
|
||||
|
||||
#"""
|
||||
#Examples
|
||||
#julia> el = Element(Quad4, [1, 2, 3, 4]);
|
||||
#julia> el([0.0, 0.0], 0.0, 1)
|
||||
#1x4 Array{Float64,2}:
|
||||
# 0.25 0.25 0.25 0.25
|
||||
#julia> el([0.0, 0.0], 0.0, 2)
|
||||
#2x8 Array{Float64,2}:
|
||||
# 0.25 0.0 0.25 0.0 0.25 0.0 0.25 0.0
|
||||
# 0.0 0.25 0.0 0.25 0.0 0.25 0.0 0.25
|
||||
#"""
|
||||
function (element::Element)(ip, time::Float64, dim::Int)
|
||||
dim == 1 && return get_basis(element, ip, time)
|
||||
Ni = vec(get_basis(element, ip, time))
|
||||
N = zeros(dim, length(element)*dim)
|
||||
for i=1:dim
|
||||
N[i,i:dim:end] += Ni
|
||||
end
|
||||
return N
|
||||
end
|
||||
|
||||
function (element::Element)(ip, time, ::Type{Val{:Jacobian}})
|
||||
X = element("geometry", time)
|
||||
J = jacobian(element.properties, X, ip)
|
||||
return J
|
||||
end
|
||||
|
||||
function (element::Element)(ip, time::Float64, ::Type{Val{:detJ}})
|
||||
J = element(ip, time, Val{:Jacobian})
|
||||
n, m = size(J)
|
||||
if n == m # volume element
|
||||
return det(J)
|
||||
end
|
||||
JT = transpose(J)
|
||||
if size(JT, 2) == 1 # boundary of 2d problem, || ∂X/∂ξ ||
|
||||
return norm(JT)
|
||||
else # manifold on 3d problem, || ∂X/∂ξ₁ × ∂X/∂ξ₂ ||
|
||||
return norm(cross(JT[:,1], JT[:,2]))
|
||||
end
|
||||
end
|
||||
|
||||
function (element::Element)(ip, time::Float64, ::Type{Val{:Grad}})
|
||||
J = element(ip, time, Val{:Jacobian})
|
||||
return inv(J)*get_dbasis(element, ip, time)
|
||||
end
|
||||
|
||||
function (element::Element)(field_name::String, ip, time::Float64, ::Type{Val{:Grad}})
|
||||
X = element("geometry", time)
|
||||
u = element(field_name, time)
|
||||
return grad(element.properties, u, X, ip)
|
||||
end
|
||||
|
||||
|
||||
function get_integration_points(element::AbstractElement{E}) where E
|
||||
# first time initialize default integration points
|
||||
if length(element.integration_points) == 0
|
||||
ips = get_integration_points(element.properties)
|
||||
element.integration_points = [IP(i, w, xi) for (i, (w, xi)) in enumerate(ips)]
|
||||
end
|
||||
return element.integration_points
|
||||
end
|
||||
|
||||
""" This is a special case, temporarily change order
|
||||
of integration scheme mainly for mass matrix.
|
||||
"""
|
||||
function get_integration_points(element::AbstractElement{E}, change_order::Int) where E
|
||||
ips = get_integration_points(element.properties, Val{change_order})
|
||||
return [IP(i, w, xi) for (i, (w, xi)) in enumerate(ips)]
|
||||
end
|
||||
|
||||
""" Find inverse isoparametric mapping of element. """
|
||||
function get_local_coordinates(element::AbstractElement, X::Vector, time::Float64; max_iterations=10, tolerance=1.0e-6)
|
||||
haskey(element, "geometry") || error("element geometry not defined, cannot calculate inverse isoparametric mapping")
|
||||
dim = size(element, 1)
|
||||
dim == length(X) || error("manifolds not supported.")
|
||||
xi = zeros(dim)
|
||||
dX = element("geometry", xi, time) - X
|
||||
for i=1:max_iterations
|
||||
J = element(xi, time, Val{:Jacobian})'
|
||||
xi -= J \ dX
|
||||
dX = element("geometry", xi, time) - X
|
||||
norm(dX) < tolerance && return xi
|
||||
end
|
||||
debug("get_local_coordinates", X, dX, xi)
|
||||
error("Unable to find inverse isoparametric mapping for element $element for X = $X")
|
||||
end
|
||||
|
||||
""" Test is X inside element. """
|
||||
function inside(element::AbstractElement{M,B}, X, time) where {M,B}
|
||||
xi = get_local_coordinates(element, X, time)
|
||||
return inside(B, xi)
|
||||
end
|
||||
|
||||
## Convenience functions
|
||||
|
||||
# element("displacement", 0.0)
|
||||
function (element::Element)(field_name::String, time::Float64)
|
||||
return interpolate(element, field_name, time)
|
||||
end
|
||||
|
||||
# element("displacement", (0.0, 0.0), 0.0)
|
||||
function (element::Element)(field_name::String, ip, time::Float64)
|
||||
return interpolate(element, field_name, ip, time)
|
||||
end
|
||||
|
||||
function element_info!(bi::BasisInfo{T}, element::AbstractElement{M,T}, ip, time) where {M,T}
|
||||
X = interpolate(element, "geometry", time)
|
||||
eval_basis!(bi, X, ip)
|
||||
return bi.J, bi.detJ, bi.N, bi.grad
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
struct Poi1 <: AbstractBasis{0} end
|
||||
|
||||
function get_basis(::E, ::Any, ::Any) where E<:AbstractElement{M,Poi1} where M
|
||||
return [1]
|
||||
end
|
||||
|
||||
function get_dbasis(::E, ::Any, ::Any) where E<:AbstractElement{M,Poi1} where M
|
||||
return [0]
|
||||
end
|
||||
|
||||
function (::Element{M,Poi1})(::Any, ::Float64, ::Type{Val{:detJ}}) where M
|
||||
return 1.0
|
||||
end
|
||||
|
||||
function get_integration_order(::Poi1)
|
||||
return 1
|
||||
end
|
||||
|
||||
function get_integration_points(::Poi1, ::Int)
|
||||
return [(1.0, (0.0,))]
|
||||
end
|
||||
|
||||
function size(::Type{Poi1})
|
||||
return (0, 1)
|
||||
end
|
||||
|
||||
function length(::Type{Poi1})
|
||||
return 1
|
||||
end
|
||||
|
||||
function get_reference_element_coordinates(::Type{Poi1})
|
||||
Vector{Float64}[[0.0]]
|
||||
end
|
||||
|
||||
function inside(::Union{Type{Seg2},Type{Seg3},Type{Quad4},
|
||||
Type{Quad8},Type{Quad9},Type{Pyr5},
|
||||
Type{Hex8},Type{Hex20},
|
||||
Type{Hex27}}, xi)
|
||||
return all(-1.0 .<= xi .<= 1.0)
|
||||
end
|
||||
|
||||
function inside(::Union{Type{Tri3},Type{Tri6},Type{Tri7},
|
||||
Type{Tet4},Type{Tet10}}, xi)
|
||||
return all(xi .>= 0.0) && (sum(xi) <= 1.0)
|
||||
end
|
||||
|
||||
function get_reference_coordinates(::E) where E<:AbstractElement{M,B} where {M,B}
|
||||
return get_reference_element_coordinates(B)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
# Default number of integration points for each element. First rule is the
|
||||
# default integration rule returned by `get_integration_points(element)`.
|
||||
# Sometimes we want to increase integration order, e.g. when integrating mass
|
||||
# matrix or boundary conditions. For that reason, additional rules are provied
|
||||
# in list, so e.g. `get_integration_points(element, 1)` returns the second rule,
|
||||
# `get_integration_points(element, 2)` third rule and so on. Rules should be
|
||||
# ordered so that picking next one integrates more accurately.
|
||||
integration_rule_mapping = (
|
||||
:Seg2 => (:GLSEG2, :GLSEG3, :GLSEG4, :GLSEG5),
|
||||
:Seg3 => (:GLSEG3, :GLSEG4, :GLSEG5),
|
||||
:NSeg => (:GLSEG2, :GLSEG3, :GLSEG4, :GLSEG5),
|
||||
:Quad4 => (:GLQUAD4, :GLQUAD9, :GLQUAD16, :GLQUAD25),
|
||||
:Quad8 => (:GLQUAD9, :GLQUAD16, :GLQUAD25),
|
||||
:Quad9 => (:GLQUAD9, :GLQUAD16, :GLQUAD25),
|
||||
:NSurf => (:GLQUAD9, :GLQUAD16, :GLQUAD25),
|
||||
:Hex8 => (:GLHEX8, :GLHEX27, :GLHEX64, :GLHEX125),
|
||||
:Hex20 => (:GLHEX27, :GLHEX64, :GLHEX125),
|
||||
:Hex27 => (:GLHEX27, :GLHEX64, :GLHEX125),
|
||||
:NSolid => (:GLHEX27, :GLHEX64, :GLHEX125),
|
||||
:Tri3 => (:GLTRI1, :GLTRI3, :GLTRI4, :GLTRI6, :GLTRI7, :GLTRI12),
|
||||
:Tri6 => (:GLTRI3, :GLTRI4, :GLTRI6, :GLTRI7, :GLTRI12),
|
||||
:Tri7 => (:GLTRI3, :GLTRI4, :GLTRI6, :GLTRI7, :GLTRI12),
|
||||
:Tet4 => (:GLTET1, :GLTET4, :GLTET5, :GLTET15),
|
||||
:Tet10 => (:GLTET4, :GLTET5, :GLTET15),
|
||||
:Pyr5 => (:GLPYR5,),
|
||||
:Wedge6 => (:GLWED6, :GLWED21),
|
||||
:Wedge15 => (:GLWED21,))
|
||||
|
||||
for (E, R) in integration_rule_mapping
|
||||
for i in 1:length(R)
|
||||
P = Val{R[i]}
|
||||
order = Val{i - 1}
|
||||
local code # Explicitly declare as local to avoid warning
|
||||
if isequal(i, 1)
|
||||
code = quote
|
||||
function get_integration_points(element::$E)
|
||||
return FEMQuad.get_quadrature_points($P)
|
||||
end
|
||||
end
|
||||
else
|
||||
code = quote
|
||||
function get_integration_points(element::$E, ::Type{$order})
|
||||
return FEMQuad.get_quadrature_points($P)
|
||||
end
|
||||
end
|
||||
end
|
||||
eval(code)
|
||||
end
|
||||
end
|
||||
|
||||
# All good codes needs a special case. Here we have it: Poi1
|
||||
function get_integration_points(::Poi1)
|
||||
[(1.0, (0.0,))]
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE
|
||||
|
||||
"""
|
||||
Compatibility shim for vendor packages that expect FEMBase types.
|
||||
|
||||
Since we've consolidated FEMBase into JuliaFEM, we need to provide
|
||||
the FEMBase module namespace for backward compatibility with code
|
||||
that uses FEMBase.function_name().
|
||||
|
||||
This creates a minimal FEMBase module with function forwarding.
|
||||
Type aliases are added after all types are defined.
|
||||
"""
|
||||
module FEMBase
|
||||
|
||||
# Note: We can only create function aliases here, not type aliases,
|
||||
# because not all types have been defined yet when this module is included.
|
||||
|
||||
# Forward declarations for functions that exist at this point
|
||||
# We'll add more after types are defined
|
||||
|
||||
end # module FEMBase
|
||||
@@ -0,0 +1,376 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
"""
|
||||
AbstractField
|
||||
|
||||
Abstract supertype for all fields in JuliaFEM.
|
||||
"""
|
||||
abstract type AbstractField end
|
||||
|
||||
function length(f::F) where F<:AbstractField
|
||||
return length(f.data)
|
||||
end
|
||||
|
||||
function size(f::F) where F<:AbstractField
|
||||
return size(f.data)
|
||||
end
|
||||
|
||||
function ==(x::F, y) where F<:AbstractField
|
||||
return ==(x.data, y)
|
||||
end
|
||||
|
||||
function ==(x, y::F) where F<:AbstractField
|
||||
return ==(x, y.data)
|
||||
end
|
||||
|
||||
function ==(x::F, y::F) where F<:AbstractField
|
||||
return ==(x.data, y.data)
|
||||
end
|
||||
|
||||
function getindex(f::F, i::Int) where F<:AbstractField
|
||||
return getindex(f.data, i)
|
||||
end
|
||||
|
||||
function interpolate_field(field::AbstractField, ::Any)
|
||||
return field.data
|
||||
end
|
||||
|
||||
function update_field!(field::AbstractField, data)
|
||||
field.data = data
|
||||
end
|
||||
|
||||
"""
|
||||
DCTI{T} <: AbstractField
|
||||
|
||||
Discrete, constant, time-invariant field.
|
||||
|
||||
This field is constant in both spatial direction and time direction,
|
||||
i.e. df/dX = 0 and df/dt = 0.
|
||||
|
||||
# Example
|
||||
|
||||
```jldoctest
|
||||
julia> DCTI(1)
|
||||
FEMBase.DCTI{Int64}(1)
|
||||
```
|
||||
"""
|
||||
mutable struct DCTI{T} <: AbstractField
|
||||
data :: T
|
||||
end
|
||||
|
||||
function getindex(field::DCTI, ::Int)
|
||||
return field.data
|
||||
end
|
||||
|
||||
"""
|
||||
DVTI{N,T} <: AbstractField
|
||||
|
||||
Discrete, variable, time-invariant field.
|
||||
|
||||
This is constant in time direction, but not in spatial direction, i.e. df/dt = 0
|
||||
but df/dX != 0. The basic structure of data is `Tuple`, and it is implicitly
|
||||
assumed that length of field matches to the number of shape functions, so that
|
||||
interpolation in spatial direction works.
|
||||
|
||||
# Example
|
||||
|
||||
```jldoctest
|
||||
julia> DVTI(1, 2, 3)
|
||||
FEMBase.DVTI{3,Int64}((1, 2, 3))
|
||||
```
|
||||
"""
|
||||
mutable struct DVTI{N,T} <: AbstractField
|
||||
data :: NTuple{N,T}
|
||||
end
|
||||
|
||||
function DVTI(data...)
|
||||
return DVTI(data)
|
||||
end
|
||||
|
||||
"""
|
||||
DCTV{T} <: AbstractField
|
||||
|
||||
Discrete, constant, time variant field. This type of field can change in time
|
||||
direction but not in spatial direction.
|
||||
|
||||
# Example
|
||||
|
||||
Field having value 5 at time 0.0 and value 10 at time 1.0:
|
||||
|
||||
```jldoctest
|
||||
julia> DCTV(0.0 => 5, 1.0 => 10)
|
||||
FEMBase.DCTV{Int64}(Pair{Float64,Int64}[0.0=>5, 1.0=>10])
|
||||
```
|
||||
|
||||
"""
|
||||
mutable struct DCTV{T} <: AbstractField
|
||||
data :: Vector{Pair{Float64,T}}
|
||||
end
|
||||
|
||||
function DCTV(data::Pair{Float64,T}...) where T
|
||||
return DCTV(collect(data))
|
||||
end
|
||||
|
||||
function update_field!(f::DCTV, data::Pair{Float64, T}) where T
|
||||
if isapprox(last(f.data).first, data.first)
|
||||
f.data[end] = data
|
||||
else
|
||||
push!(f.data, data)
|
||||
end
|
||||
end
|
||||
|
||||
function interpolate_field(field::DCTV, time)
|
||||
time < first(field.data).first && return first(field.data).second
|
||||
time > last(field.data).first && return last(field.data).second
|
||||
for i=reverse(1:length(field))
|
||||
isapprox(field.data[i].first, time) && return field.data[i].second
|
||||
end
|
||||
for i=length(field.data):-1:2
|
||||
t0 = field.data[i-1].first
|
||||
t1 = field.data[i].first
|
||||
if t0 < time < t1
|
||||
y0 = field.data[i-1].second
|
||||
y1 = field.data[i].second
|
||||
dy = y1-y0
|
||||
dt = t1-t0
|
||||
return y0 + (time-t0)*dy/dt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
DVTV{N,T} <: AbstractField
|
||||
|
||||
Discrete, variable, time variant field. The most general discrete field can
|
||||
change in both temporal and spatial direction.
|
||||
|
||||
# Example
|
||||
|
||||
```jldoctest
|
||||
julia> DVTV(0.0 => (1, 2), 1.0 => (2, 3))
|
||||
FEMBase.DVTV{2,Int64}(Pair{Float64,Tuple{Int64,Int64}}[0.0=>(1, 2), 1.0=>(2, 3)])
|
||||
```
|
||||
"""
|
||||
mutable struct DVTV{N,T} <: AbstractField
|
||||
data :: Vector{Pair{Float64,NTuple{N,T}}}
|
||||
end
|
||||
|
||||
function DVTV(data::Pair{Float64,NTuple{N,T}}...) where {N,T}
|
||||
return DVTV(collect(data))
|
||||
end
|
||||
|
||||
function update_field!(f::DVTV, data::Pair{Float64, NTuple{N,T}}) where {N,T}
|
||||
if isapprox(last(f.data).first, data.first)
|
||||
f.data[end] = data
|
||||
else
|
||||
push!(f.data, data)
|
||||
end
|
||||
end
|
||||
|
||||
function interpolate_field(field::DVTV{N,T}, time) where {N,T}
|
||||
time < first(field.data).first && return first(field.data).second
|
||||
time > last(field.data).first && return last(field.data).second
|
||||
for i=reverse(1:length(field))
|
||||
isapprox(field.data[i].first, time) && return field.data[i].second
|
||||
end
|
||||
for i=length(field.data):-1:2
|
||||
t0 = field.data[i-1].first
|
||||
t1 = field.data[i].first
|
||||
if t0 < time < t1
|
||||
y0 = field.data[i-1].second
|
||||
y1 = field.data[i].second
|
||||
dt = t1-t0
|
||||
return map((a,b) -> a + (time-t0)*(b-a)/dt, y0, y1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
CVTV <: AbstractField
|
||||
|
||||
Continuous, variable, time variant field.
|
||||
|
||||
# Example
|
||||
|
||||
```jldoctest
|
||||
julia> f = CVTV((xi,t) -> xi*t)
|
||||
FEMBase.CVTV(#1)
|
||||
```
|
||||
"""
|
||||
mutable struct CVTV <: AbstractField
|
||||
data :: Function
|
||||
end
|
||||
|
||||
function (f::CVTV)(xi, time)
|
||||
return f.data(xi, time)
|
||||
end
|
||||
|
||||
"""
|
||||
DVTId(X::Dict)
|
||||
|
||||
Discrete, variable, time invariant dictionary field.
|
||||
"""
|
||||
mutable struct DVTId{T} <: AbstractField
|
||||
data :: Dict{Int, T}
|
||||
end
|
||||
|
||||
function update_field!(field::DVTId{T}, data::Dict{Int, T}) where T
|
||||
merge!(field.data, data)
|
||||
end
|
||||
|
||||
"""
|
||||
DVTVd(time => data::Dict)
|
||||
|
||||
Discrete, variable, time variant dictionary field.
|
||||
"""
|
||||
mutable struct DVTVd{T} <: AbstractField
|
||||
data :: Vector{Pair{Float64,Dict{Int,T}}}
|
||||
end
|
||||
|
||||
function DVTVd(data::Pair{Float64,Dict{Int,T}}...) where T
|
||||
return DVTVd(collect(data))
|
||||
end
|
||||
|
||||
function interpolate_field(field::DVTVd{T}, time) where T
|
||||
time >= last(field.data).first && return last(field.data).second
|
||||
time <= first(field.data).first && return first(field.data).second
|
||||
for i=reverse(1:length(field))
|
||||
isapprox(field.data[i].first, time) && return field.data[i].second
|
||||
end
|
||||
for i=length(field.data):-1:2
|
||||
t0 = field.data[i-1].first
|
||||
t1 = field.data[i].first
|
||||
if t0 < time < t1
|
||||
y0 = field.data[i-1].second
|
||||
y1 = field.data[i].second
|
||||
f = (time-t0)/(t1-t0)
|
||||
new_data = empty(y0)
|
||||
for i in keys(y0)
|
||||
new_data[i] = f*y0[i] + (1-f)*y1[i]
|
||||
end
|
||||
return new_data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function update_field!(f::DVTVd, data::Pair{Float64,Dict{Int,T}}) where T
|
||||
if isapprox(last(f.data).first, data.first)
|
||||
f.data[end] = data
|
||||
else
|
||||
push!(f.data, data)
|
||||
end
|
||||
end
|
||||
|
||||
function new_field(data)
|
||||
return DCTI(data)
|
||||
end
|
||||
|
||||
function new_field(data...)
|
||||
return DVTI(data)
|
||||
end
|
||||
|
||||
function new_field(data::NTuple{N,T}) where {N,T}
|
||||
return DVTI(data)
|
||||
end
|
||||
|
||||
function new_field(data::Pair{Float64,T}...) where T
|
||||
return DCTV(collect(data))
|
||||
end
|
||||
|
||||
function new_field(data::Pair{Float64,NTuple{N,T}}...) where {N,T}
|
||||
return DVTV(collect(data))
|
||||
end
|
||||
|
||||
function new_field(data::Function)
|
||||
return CVTV(data)
|
||||
end
|
||||
|
||||
function new_field(data::Pair{Int, T}...) where T
|
||||
return DVTId(Dict(data))
|
||||
end
|
||||
|
||||
function new_field(data::Pair{Float64, NTuple{N, Pair{Int, T}}}...) where {N,T}
|
||||
return DVTVd(collect(t => Dict(d) for (t, d) in data))
|
||||
end
|
||||
|
||||
function new_field(data::Dict{Int,T}) where T
|
||||
return DVTId(data)
|
||||
end
|
||||
|
||||
function new_field(data::Pair{Float64, Dict{Int, T}}...) where T
|
||||
return DVTVd(collect(data))
|
||||
end
|
||||
|
||||
"""
|
||||
field(x)
|
||||
|
||||
Create new field. Field type is deduced from data type.
|
||||
"""
|
||||
function field(data...)
|
||||
return new_field(data...)
|
||||
end
|
||||
|
||||
"""
|
||||
interpolate(field, time)
|
||||
|
||||
Interpolate field in time direction.
|
||||
|
||||
# Examples
|
||||
|
||||
For time invariant fields [`DCTI`](@ref), [`DVTI`](@ref), [`DVTId`](@ref)
|
||||
solution is trivially the data inside field as fields does not depend from
|
||||
the time:
|
||||
|
||||
```jldoctest
|
||||
julia> a = field(1.0)
|
||||
FEMBase.DCTI{Float64}(1.0)
|
||||
|
||||
julia> interpolate(a, 0.0)
|
||||
1.0
|
||||
```
|
||||
|
||||
```jldoctest
|
||||
julia> a = field((1.0, 2.0))
|
||||
FEMBase.DVTI{2,Float64}((1.0, 2.0))
|
||||
|
||||
julia> interpolate(a, 0.0)
|
||||
(1.0, 2.0)
|
||||
```
|
||||
|
||||
```jldoctest
|
||||
julia> a = field(1=>1.0, 2=>2.0)
|
||||
FEMBase.DVTId{Float64}(Dict(2=>2.0,1=>1.0))
|
||||
|
||||
julia> interpolate(a, 0.0)
|
||||
Dict{Int64,Float64} with 2 entries:
|
||||
2 => 2.0
|
||||
1 => 1.0
|
||||
```
|
||||
|
||||
DVTId trivial solution is returned. For time variant fields DCTV, DVTV, DVTVd
|
||||
linear interpolation is performed.
|
||||
|
||||
# Other notes
|
||||
|
||||
First algorithm checks that is time out of range, i.e. time is smaller than
|
||||
time of first frame or larger than last frame. If that is the case, return
|
||||
first or last frame. Secondly algorithm finds is given time exact match to
|
||||
time of some frame and return that frame. At last, we find correct bin so
|
||||
that t0 < time < t1 and use linear interpolation.
|
||||
|
||||
"""
|
||||
function interpolate(field::AbstractField, time)
|
||||
return interpolate_field(field, time)
|
||||
end
|
||||
|
||||
"""
|
||||
interpolate(a, b)
|
||||
|
||||
A helper function for interpolate routines. Given iterables `a` and `b`,
|
||||
calculate c = aᵢbᵢ. Length of `a` can be less than `b`, but not vice versa.
|
||||
"""
|
||||
function interpolate(a, b)
|
||||
@assert length(a) <= length(b)
|
||||
return sum(a[i]*b[i] for i=1:length(a))
|
||||
end
|
||||
+2
-2
@@ -122,7 +122,7 @@ end
|
||||
Add an element into the mesh. ´elid´ is the element id, ´eltype´ is the type of
|
||||
the element and ´connectivity´ is the connectivity of the element.
|
||||
"""
|
||||
function FEMBase.add_element!(mesh::Mesh, elid, eltype, connectivity)
|
||||
function add_element!(mesh::Mesh, elid, eltype, connectivity)
|
||||
mesh.elements[elid] = connectivity
|
||||
mesh.element_types[elid] = eltype
|
||||
return nothing
|
||||
@@ -133,7 +133,7 @@ end
|
||||
|
||||
Add elements into the mesh.
|
||||
"""
|
||||
function FEMBase.add_elements!(mesh::Mesh, elements::Dict{Int, Tuple{Symbol, Vector{Int}}})
|
||||
function add_elements!(mesh::Mesh, elements::Dict{Int, Tuple{Symbol, Vector{Int}}})
|
||||
for (elid, (eltype, elcon)) in elements
|
||||
add_element!(mesh, elid, eltype, elcon)
|
||||
end
|
||||
|
||||
+2
-2
@@ -572,7 +572,7 @@ function has_converged(solver::Solver{Nonlinear})
|
||||
end
|
||||
|
||||
""" Default solver for quasistatic nonlinear problems. """
|
||||
function FEMBase.run!(solver::Solver{Nonlinear})
|
||||
function run!(solver::Solver{Nonlinear})
|
||||
|
||||
time = solver.properties.time
|
||||
problems = get_problems(solver)
|
||||
@@ -638,7 +638,7 @@ function Linear()
|
||||
return Linear(0.0)
|
||||
end
|
||||
|
||||
function FEMBase.run!(analysis::Analysis{Linear})
|
||||
function run!(analysis::Analysis{Linear})
|
||||
time = analysis.properties.time
|
||||
@info("Running linear quasistatic analysis `$(analysis.name)` at time $time.")
|
||||
problems = get_problems(analysis)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
mutable struct LinearSystem{Tv, Ti<:Integer}
|
||||
M :: SparseMatrixCSC{Tv, Ti}
|
||||
K :: SparseMatrixCSC{Tv, Ti}
|
||||
Kg :: SparseMatrixCSC{Tv, Ti}
|
||||
C1 :: SparseMatrixCSC{Tv, Ti}
|
||||
C2 :: SparseMatrixCSC{Tv, Ti}
|
||||
D :: SparseMatrixCSC{Tv, Ti}
|
||||
f :: SparseVector{Tv, Ti}
|
||||
fg :: SparseVector{Tv, Ti}
|
||||
g :: SparseVector{Tv, Ti}
|
||||
u :: SparseVector{Tv, Ti}
|
||||
la :: SparseVector{Tv, Ti}
|
||||
dim :: Int
|
||||
end
|
||||
|
||||
function LinearSystem(dim::Int)
|
||||
return LinearSystem(spzeros(dim, dim), spzeros(dim, dim),
|
||||
spzeros(dim, dim), spzeros(dim, dim),
|
||||
spzeros(dim, dim), spzeros(dim, dim),
|
||||
spzeros(dim), spzeros(dim), spzeros(dim),
|
||||
spzeros(dim), spzeros(dim), dim)
|
||||
end
|
||||
|
||||
abstract type AbstractLinearSystemSolver end
|
||||
|
||||
function solve!(::LinearSystem, ::Solver) where Solver<:AbstractLinearSystemSolver
|
||||
@info("This is a placeholder function for solving linear systems. To solve " *
|
||||
"linear systems, you must define a function " *
|
||||
"solve!(system::LinearSystem, solver::$Solver)")
|
||||
end
|
||||
|
||||
function can_solve(::LinearSystem, ::Solver) where Solver<:AbstractLinearSystemSolver
|
||||
return (true, "OK")
|
||||
end
|
||||
|
||||
function solve!(ls::LinearSystem, solvers::Vector{S}) where S<:AbstractLinearSystemSolver
|
||||
for solver in solvers
|
||||
Solver = typeof(solver)
|
||||
cansolve, msg = can_solve(ls, solver)
|
||||
if !cansolve
|
||||
@info("Solver $Solver cannot solve linear system: $msg")
|
||||
continue
|
||||
end
|
||||
timeit("solve linear system using solver $Solver") do
|
||||
solve!(ls, solver)
|
||||
end
|
||||
return
|
||||
end
|
||||
error("Failed to solve linear system.")
|
||||
end
|
||||
+11
-3
@@ -29,6 +29,9 @@ end
|
||||
|
||||
A helper function to calculate P = D^-1*M
|
||||
"""
|
||||
# TEMPORARILY DISABLED: Vendor package Mortar2D expects old FEMBase.AbstractProblem
|
||||
# TODO: Re-enable after vendor packages are consolidated or updated
|
||||
#=
|
||||
function calc_projection(problem::T) where
|
||||
{T<:Union{Problem{Mortar}, Problem{Mortar2D}}}
|
||||
|
||||
@@ -53,8 +56,9 @@ function calc_projection(problem::T) where
|
||||
|
||||
return s, m, P
|
||||
end
|
||||
=#
|
||||
|
||||
function FEMBase.eliminate_boundary_conditions!(problem::P, K, M, f) where {P}
|
||||
function eliminate_boundary_conditions!(problem::P, K, M, f) where {P}
|
||||
isempty(problem.assembly.C2) && return nothing
|
||||
C1 = sparse(problem.assembly.C1)
|
||||
C2 = sparse(problem.assembly.C2)
|
||||
@@ -76,7 +80,10 @@ end
|
||||
|
||||
Eliminate Mortar boundary condition from matrices K, M and force vector f.
|
||||
"""
|
||||
function FEMBase.eliminate_boundary_conditions!(problem::T, K, M, f) where
|
||||
# TEMPORARILY DISABLED: Vendor package Mortar2D expects old FEMBase.AbstractProblem
|
||||
# TODO: Re-enable after vendor packages are consolidated or updated
|
||||
#=
|
||||
function eliminate_boundary_conditions!(problem::T, K, M, f) where
|
||||
{T <: Union{Problem{Mortar}, Problem{Mortar2D}}}
|
||||
@info("Eliminating mesh tie constraint $(problem.name) using static condensation")
|
||||
s, m, P = calc_projection(problem)
|
||||
@@ -89,8 +96,9 @@ function FEMBase.eliminate_boundary_conditions!(problem::T, K, M, f) where
|
||||
M[:,:] .= Q'*M*Q
|
||||
return nothing
|
||||
end
|
||||
=#
|
||||
|
||||
function FEMBase.run!(solver::Solver{Modal})
|
||||
function run!(solver::Solver{Modal})
|
||||
time = solver.properties.time
|
||||
problems = get_problems(solver)
|
||||
properties = solver.properties
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/FEMBase.jl/blob/master/LICENSE
|
||||
|
||||
using SparseArrays
|
||||
import SparseArrays: sparse, sparsevec
|
||||
|
||||
mutable struct SparseMatrixCOO{T<:Real}
|
||||
I :: Vector{Int}
|
||||
J :: Vector{Int}
|
||||
V :: Vector{T}
|
||||
end
|
||||
|
||||
const SparseVectorCOO = SparseMatrixCOO
|
||||
|
||||
function SparseMatrixCOO()
|
||||
return SparseMatrixCOO{Float64}([], [], [])
|
||||
end
|
||||
|
||||
function SparseVectorCOO(I::Vector, V::Vector)
|
||||
return SparseVectorCOO(I, fill!(similar(I), 1), V)
|
||||
end
|
||||
|
||||
function convert(::Type{SparseMatrixCOO}, A::SparseMatrixCSC)
|
||||
return SparseMatrixCOO(findnz(A)...)
|
||||
end
|
||||
|
||||
function convert(::Type{SparseVectorCOO}, A::SparseVector)
|
||||
return SparseVectorCOO(findnz(A)...)
|
||||
end
|
||||
|
||||
function convert(::Type{SparseMatrixCOO}, A::Matrix)
|
||||
idx = findall(!iszero, A)
|
||||
I = getindex.(idx, 1)
|
||||
J = getindex.(idx, 2)
|
||||
V = [A[i] for i in idx]
|
||||
return SparseMatrixCOO(I, J, V)
|
||||
end
|
||||
|
||||
function convert(::Type{SparseMatrixCOO}, b::Vector)
|
||||
I = findall(!iszero, b)
|
||||
J = fill(1, size(I))
|
||||
V = b[I]
|
||||
return SparseMatrixCOO(I, J, V)
|
||||
end
|
||||
|
||||
SparseArrays.sparse(A::SparseMatrixCOO) = sparse(A.I, A.J, A.V)
|
||||
SparseArrays.sparse(A::SparseMatrixCOO, n::Int, m::Int) = sparse(A.I, A.J, A.V, n, m)
|
||||
SparseArrays.sparse(A::SparseMatrixCOO, n::Int, m::Int, f::Function) = sparse(A.I, A.J, A.V, n, m, f)
|
||||
Base.Matrix(A::SparseMatrixCOO) = Matrix(sparse(A))
|
||||
Base.Matrix(A::SparseMatrixCOO, n::Int, m::Int) = Matrix(sparse(A, n, m))
|
||||
|
||||
SparseArrays.sparsevec(b::SparseVectorCOO) = sparsevec(b.I, b.V)
|
||||
SparseArrays.sparsevec(b::SparseVectorCOO, n::Int) = sparsevec(b.I, b.V, n)
|
||||
Base.Vector(b::SparseVectorCOO) = Vector(sparsevec(b))
|
||||
Base.Vector(b::SparseVectorCOO, n::Int) = Vector(sparsevec(b, n))
|
||||
|
||||
function add!(A::SparseMatrixCOO, I::Int, J::Int, V::Float64)
|
||||
push!(A.I, I)
|
||||
push!(A.J, J)
|
||||
push!(A.V, V)
|
||||
return nothing
|
||||
end
|
||||
|
||||
function add!(A::SparseMatrixCOO, I::Int, V::Float64)
|
||||
push!(A.I, I)
|
||||
push!(A.J, 1)
|
||||
push!(A.V, V)
|
||||
return nothing
|
||||
end
|
||||
|
||||
function empty!(A::SparseMatrixCOO)
|
||||
empty!(A.I)
|
||||
empty!(A.J)
|
||||
empty!(A.V)
|
||||
return nothing
|
||||
end
|
||||
|
||||
function append!(A::SparseMatrixCOO, B::SparseMatrixCOO)
|
||||
append!(A.I, B.I)
|
||||
append!(A.J, B.J)
|
||||
append!(A.V, B.V)
|
||||
return nothing
|
||||
end
|
||||
|
||||
function isempty(A::SparseMatrixCOO)
|
||||
return isempty(A.I) && isempty(A.J) && isempty(A.V)
|
||||
end
|
||||
|
||||
"""
|
||||
add!(K, dofs1, dofs2, ke)
|
||||
|
||||
Add local element matrix `ke` to sparse matrix `K` for indices defined by `dofs1`
|
||||
and `dofs2`. This basically does `A[dofs1, dofs2] = A[dofs1, dofs2] + data`.
|
||||
|
||||
# Examples
|
||||
|
||||
```julia
|
||||
S = [3, 4]
|
||||
M = [6, 7, 8]
|
||||
ke = [5 6 7; 8 9 10]
|
||||
K = SparseMatrixCOO()
|
||||
add!(K, S, M, ke)
|
||||
Matrix(A)
|
||||
|
||||
# output
|
||||
|
||||
4x8 Array{Float64,2}:
|
||||
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
|
||||
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
|
||||
0.0 0.0 0.0 0.0 0.0 5.0 6.0 7.0
|
||||
0.0 0.0 0.0 0.0 0.0 8.0 9.0 10.0
|
||||
```
|
||||
"""
|
||||
function add!(A::SparseMatrixCOO, dofs1::AbstractVector{Int}, dofs2::AbstractVector{Int}, data)
|
||||
n, m = length(dofs1), length(dofs2)
|
||||
@assert length(data) == n*m
|
||||
k = 1
|
||||
for j=1:m
|
||||
for i=1:n
|
||||
add!(A, dofs1[i], dofs2[j], data[k])
|
||||
k += 1
|
||||
end
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
""" Add sparse matrix of CSC to COO. """
|
||||
function add!(A::SparseMatrixCOO, B::SparseMatrixCSC)
|
||||
i, j, v = findnz(B)
|
||||
C = SparseMatrixCOO(i, j, v)
|
||||
append!(A, C)
|
||||
end
|
||||
|
||||
""" Add new data to COO Sparse vector. """
|
||||
function add!(A::SparseMatrixCOO, dofs::Vector{Int}, data::Array{Float64}, dim::Int=1)
|
||||
if length(dofs) != length(data)
|
||||
@error("Dimension mismatch when adding data to sparse vector!", dofs, data)
|
||||
error("Simulation stopped.")
|
||||
end
|
||||
append!(A.I, dofs)
|
||||
append!(A.J, dim*ones(Int, length(dofs)))
|
||||
append!(A.V, vec(data))
|
||||
end
|
||||
|
||||
""" Add SparseVector to SparseVectorCOO. """
|
||||
function add!(a::SparseVectorCOO, b::SparseVector)
|
||||
i, v = findnz(b)
|
||||
c = SparseVectorCOO(i, v)
|
||||
append!(a, c)
|
||||
return
|
||||
end
|
||||
|
||||
"""
|
||||
get_nonzero_rows(A)
|
||||
|
||||
Returns indices of all nonzero rows from a sparse matrix `A`.
|
||||
"""
|
||||
function get_nonzero_rows(A)
|
||||
return sort(unique(A.rowval))
|
||||
end
|
||||
|
||||
"""
|
||||
get_nonzero_columns(A)
|
||||
|
||||
Returns indices of all nonzero columns from a sparse matrix `A`.
|
||||
"""
|
||||
function get_nonzero_columns(A)
|
||||
return get_nonzero_rows(copy(transpose(A)))
|
||||
end
|
||||
|
||||
function size(A::SparseMatrixCOO)
|
||||
isempty(A) && return (0, 0)
|
||||
return maximum(A.I), maximum(A.J)
|
||||
end
|
||||
|
||||
function size(A::SparseMatrixCOO, idx::Int)
|
||||
return size(A)[idx]
|
||||
end
|
||||
|
||||
""" Resize sparse matrix A to (higher) dimension n x m. """
|
||||
function resize_sparse(A, n, m)
|
||||
idx = findall(!iszero, A)
|
||||
I = getindex.(idx, 1)
|
||||
J = getindex.(idx, 2)
|
||||
V = [A[i] for i in idx]
|
||||
return sparse(I, J, V, n, m)
|
||||
end
|
||||
|
||||
""" Resize sparse vector b to (higher) dimension n. """
|
||||
function resize_sparsevec(b, n)
|
||||
return sparsevec(b.nzind, b.nzval, n)
|
||||
end
|
||||
|
||||
""" Approximative comparison of two matrices A and B. """
|
||||
function isapprox(A::SparseMatrixCOO, B::SparseMatrixCOO)
|
||||
A2 = sparse(A)
|
||||
B2 = sparse(B, size(A2)...)
|
||||
return isapprox(A2, B2)
|
||||
end
|
||||
|
||||
isapprox(A::SparseMatrixCOO, B) = isapprox(Matrix(A), B)
|
||||
isapprox(A, B::SparseMatrixCOO) = isapprox(A, Matrix(B))
|
||||
Reference in New Issue
Block a user