From ecf332eccfa97994c5d9ee8104350e20a9cbcd12 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 26 May 2020 16:40:28 +0300 Subject: [PATCH 01/66] Clean the full workflow example a bit For now, the new version is stored as abstractmaterial_full_workflow2.jl. --- examples/abstractmaterial_full_workflow2.jl | 477 ++++++++++++++++++++ 1 file changed, 477 insertions(+) create mode 100644 examples/abstractmaterial_full_workflow2.jl diff --git a/examples/abstractmaterial_full_workflow2.jl b/examples/abstractmaterial_full_workflow2.jl new file mode 100644 index 0000000..b4356d4 --- /dev/null +++ b/examples/abstractmaterial_full_workflow2.jl @@ -0,0 +1,477 @@ +module MyTest +export test_chaboche + +using Tensors +using Parameters +using NLsolve +using ForwardDiff +using Profile + +abstract type AbstractMaterial end +abstract type AbstractMaterialState end + +@generated function Base.:+(state::T, dstate::T) where {T <: AbstractMaterialState} + expr = [:(state.$p+ dstate.$p) for p in fieldnames(T)] + return :(T($(expr...))) +end + +@with_kw mutable struct ChabocheDriverState <: AbstractMaterialState + time :: Float64 = zero(Float64) + strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) +end + +@with_kw struct ChabocheParameterState <: AbstractMaterialState + E :: Float64 = 0.0 + nu :: Float64 = 0.0 + R0 :: Float64 = 0.0 + Kn :: Float64 = 0.0 + nn :: Float64 = 0.0 + C1 :: Float64 = 0.0 + D1 :: Float64 = 0.0 + C2 :: Float64 = 0.0 + D2 :: Float64 = 0.0 + Q :: Float64 = 0.0 + b :: Float64 = 0.0 +end + +@with_kw struct ChabocheVariableState <: AbstractMaterialState + stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + X1 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + X2 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + cumeq :: Float64 = zero(Float64) + R :: Float64 = zero(Float64) + jacobian :: SymmetricTensor{4,3} = zero(SymmetricTensor{4,3,Float64}) +end + +@with_kw mutable struct Chaboche <: AbstractMaterial + drivers :: ChabocheDriverState = ChabocheDriverState() + ddrivers :: ChabocheDriverState = ChabocheDriverState() + variables :: ChabocheVariableState = ChabocheVariableState() + variables_new :: ChabocheVariableState = ChabocheVariableState() + parameters :: ChabocheParameterState = ChabocheParameterState() + dparameters :: ChabocheParameterState = ChabocheParameterState() +end + +@with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState + time :: Float64 = zero(Float64) + strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) +end + +@with_kw struct IdealPlasticParameterState <: AbstractMaterialState + youngs_modulus :: Float64 = zero(Float64) + poissons_ratio :: Float64 = zero(Float64) + yield_stress :: Float64 = zero(Float64) +end + +@with_kw struct IdealPlasticVariableState <: AbstractMaterialState + stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + cumeq :: Float64 = zero(Float64) +end + +function update!(material::M) where {M <: AbstractMaterial} + material.drivers += material.ddrivers + material.parameters += material.dparameters + material.variables = material.variables_new + # material.ddrivers = typeof(material.ddrivers)() + # material.dparameters = typeof(material.dparameters)() + # material.variables_new = typeof(material.variables_new)() + reset!(material) +end + +function reset!(material::M) where {M <: AbstractMaterial} + material.ddrivers = typeof(material.ddrivers)() + material.dparameters = typeof(material.dparameters)() + material.variables_new = typeof(material.variables_new)() +end + +# @with_kw mutable struct Material{M <: AbstractMaterial} +# drivers :: DriverState{M} = DriverState{M}() +# ddrivers :: DriverState{M} = DriverState{M}() +# variables :: VariableState{M} = VariableState{M}() +# dvariables :: VariableState{M} = VariableState{M}() +# parameters :: ParameterState{M} = ParameterState{M}() +# dparameters :: ParameterState{M} = ParameterState{M}() +# end + +@with_kw mutable struct IdealPlastic <: AbstractMaterial + drivers :: IdealPlasticDriverState = IdealPlasticDriverState() + ddrivers :: IdealPlasticDriverState = IdealPlasticDriverState() + variables :: IdealPlasticVariableState = IdealPlasticVariableState() + variables_new :: IdealPlasticVariableState = IdealPlasticVariableState() + parameters :: IdealPlasticParameterState = IdealPlasticParameterState() + dparameters :: IdealPlasticParameterState = IdealPlasticParameterState() +end + + +mat = Chaboche() +mat2 = IdealPlastic() + +function isotropic_elasticity_tensor(lambda, mu) + delta(i,j) = i==j ? 1.0 : 0.0 + g(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k)) + jacobian = SymmetricTensor{4, 3, Float64}(g) + return jacobian +end + +function integrate_material!(material::Chaboche) + p = material.parameters + v = material.variables + dd = material.ddrivers + d = material.drivers + @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p + mu = E/(2.0*(1.0+nu)) + lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + + @unpack strain, time = d + dstrain = dd.strain + dtime = dd.time + @unpack stress, X1, X2, plastic_strain, cumeq, R, jacobian = v + + jacobian = isotropic_elasticity_tensor(lambda, mu) + + stress += dcontract(jacobian, dstrain) + seff = stress - X1 - X2 + seff_dev = dev(seff) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R) + if f > 0.0 + g! = create_nonlinear_system_of_equations(material) + x0 = [tovoigt(stress); R; tovoigt(X1); tovoigt(X2)] + F = similar(x0) + res = nlsolve(g!, x0; autodiff = :forward) + x = res.zero + res.f_converged || error("Nonlinear system of equations did not converge!") + + stress = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[1:6]) + R = x[7] + X1 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[8:13]) + X2 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[14:19]) + seff = stress - X1 - X2 + seff_dev = dev(seff) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R) + dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn + dp = dotp*dtime + n = 1.5*seff_dev/norm(seff_dev) + + plastic_strain += dp*n + cumeq += dp + # Compute Jacobian + function residuals(x) + F = similar(x) + g!(F, x) + return F + end + drdx = ForwardDiff.jacobian(residuals, x) + drde = zeros((length(x),6)) + drde[1:6, 1:6] = -tovoigt(jacobian) + jacobian = fromvoigt(SymmetricTensor{4,3}, (drdx\drde)[1:6, 1:6]) + end + variables_new = ChabocheVariableState(stress = stress, + X1 = X1, + X2 = X2, + R = R, + plastic_strain = plastic_strain, + cumeq = cumeq, + jacobian = jacobian) + material.variables_new = variables_new +end + +function create_nonlinear_system_of_equations(material::Chaboche) + p = material.parameters + v = material.variables + dd = material.ddrivers + d = material.drivers + @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p + mu = E/(2.0*(1.0+nu)) + lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + + @unpack strain, time = d + dstrain = dd.strain + dtime = dd.time + @unpack stress, X1, X2, plastic_strain, cumeq, R = v + + function g!(F, x::Vector{T}) where {T} # System of non-linear equations + jacobian = isotropic_elasticity_tensor(lambda, mu) + stress_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[1:6]) + R_ = x[7] + X1_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[8:13]) + X2_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[14:19]) + + seff = stress_ - X1_ - X2_ + seff_dev = dev(seff) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R_) + + dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn + dp = dotp*dtime + n = 1.5*seff_dev/norm(seff_dev) + dstrain_plastic = dp*n + tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain - dstrain_plastic)) + F[7] = R - R_ + b*(Q-R_)*dp + if isapprox(C1, 0.0) + tovoigt!(view(F,8:13),X1 - X1_) + else + tovoigt!(view(F,8:13), X1 - X1_ + 2.0/3.0*C1*dp*(n - 1.5*D1/C1*X1_)) + end + if isapprox(C2, 0.0) + tovoigt!(view(F,14:19), X2 - X2_) + else + tovoigt!(view(F, 14:19), X2 - X2_ + 2.0/3.0*C2*dp*(n - 1.5*D2/C2*X2_)) + end + end + return g! +end + +function simple_integration_test() + parameters = ChabocheParameterState(E = 200.0e3, + nu = 0.3, + R0 = 100.0, + Kn = 100.0, + nn = 10.0, + C1 = 10000.0, + D1 = 100.0, + C2 = 50000.0, + D2 = 1000.0, + Q = 50.0, + b = 0.1) + + dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) + chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + + chabmat.ddrivers = ddrivers + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + + chabmat.ddrivers = ddrivers + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" +end + +using DelimitedFiles, Test +path = joinpath(@__DIR__, "one_elem_disp_chaboche", "unitelement_results.rpt") +data = readdlm(path, Float64; skipstart=4) +ts = data[:,1] +s11_ = data[:,2] +s12_ = data[:,3] +s13_ = data[:,4] +s22_ = data[:,5] +s23_ = data[:,6] +s33_ = data[:,7] +e11_ = data[:,8] +e12_ = data[:,9] +e13_ = data[:,10] +e22_ = data[:,11] +e23_ = data[:,12] +e33_ = data[:,13] + +strains = [[e11_[i], e22_[i], e33_[i], e23_[i], e13_[i], e12_[i]] for i in 1:length(ts)] + +function test_chaboche() + parameters = ChabocheParameterState(E = 200.0e3, + nu = 0.3, + R0 = 100.0, + Kn = 100.0, + nn = 10.0, + C1 = 10000.0, + D1 = 100.0, + C2 = 50000.0, + D2 = 1000.0, + Q = 50.0, + b = 0.1) + chabmat = Chaboche(parameters = parameters) + s33s = [chabmat.variables.stress[3,3]] + for i=2:length(ts) + dtime = ts[i]-ts[i-1] + dstrain = fromvoigt(SymmetricTensor{2,3,Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) + chabmat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) + integrate_material!(chabmat) + update!(chabmat) + push!(s33s, chabmat.variables.stress[3,3]) + end + @test isapprox(s33s, s33_; rtol=0.05) + print(s33s) + print(PROGRAM_FILE, @__FILE__) +end + +# test_chaboche() +# Profile.clear_malloc_data() +# test_chaboche() + +# using BenchmarkTools +# @btime test_chaboche() + +function simple_integration_test_fd_tangent() + parameters = ChabocheParameterState(E = 200.0e3, + nu = 0.3, + R0 = 100.0, + Kn = 100.0, + nn = 10.0, + C1 = 10000.0, + D1 = 100.0, + C2 = 50000.0, + D2 = 1000.0, + Q = 50.0, + b = 0.1) + + dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) + chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) + + function get_stress(dstrain) + chabmat.ddrivers.strain = dstrain + integrate_material!(chabmat) + return chabmat.variables_new.stress + end + # stress = get_stress(0.25*dstrain_dtime) + # @info "stress = $stress" + + D, dstress = Tensors.gradient(get_stress, 0.25*dstrain_dtime, :all) + @info "D_mat = $(tovoigt(chabmat.variables_new.jacobian))" + @info "D = $(tovoigt(D))" + + chabmat.variables_new = typeof(chabmat.variables_new)() + chabmat.ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + + function get_stress(dstrain) + chabmat.ddrivers.strain = dstrain + integrate_material!(chabmat) + return chabmat.variables_new.stress + end + # stress = get_stress(0.25*dstrain_dtime) + # @info "stress = $stress" + + D, dstress = Tensors.gradient(get_stress, 0.25*dstrain_dtime, :all) + @info "D = $(tovoigt(D))" + chabmat.variables_new = typeof(chabmat.variables_new)() + chabmat.ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + function get_stress(dstrain) + chabmat.ddrivers.strain = dstrain + integrate_material!(chabmat) + return chabmat.variables_new.stress + end + # stress = get_stress(0.25*dstrain_dtime) + # @info "stress = $stress" + + D, dstress = Tensors.gradient(get_stress, 0.25*dstrain_dtime, :all) + @info "D = $(tovoigt(D))" + chabmat.variables_new = typeof(chabmat.variables_new)() + chabmat.ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + function get_stress(dstrain) + chabmat.ddrivers.strain = dstrain + integrate_material!(chabmat) + return chabmat.variables_new.stress + end + # stress = get_stress(0.25*dstrain_dtime) + # @info "stress = $stress" + + D, dstress = Tensors.gradient(get_stress, 0.25*dstrain_dtime, :all) + @info "D = $(tovoigt(D))" +end + +# simple_integration_test_fd_tangent() + +function simple_integration_test_fd_tangent2() + parameters = ChabocheParameterState(E = 200.0e3, + nu = 0.3, + R0 = 100.0, + Kn = 100.0, + nn = 10.0, + C1 = 10000.0, + D1 = 100.0, + C2 = 50000.0, + D2 = 1000.0, + Q = 50.0, + b = 0.1) + + dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) + chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + + chabmat.ddrivers = ddrivers + integrate_material!(chabmat) + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + + chabmat.ddrivers = ddrivers + integrate_material!(chabmat) + g! = create_nonlinear_system_of_equations(chabmat) + function residuals(x) + F = similar(x) + g!(F, x) + return F + end + + x0 = [tovoigt(chabmat.variables_new.stress); chabmat.variables_new.R; tovoigt(chabmat.variables_new.X1); tovoigt(chabmat.variables_new.X2)] + drdx = ForwardDiff.jacobian(residuals, x0) + @info "size(drdx) = $(size(drdx))" + @info "drdx = $drdx" + @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = parameters + mu = E/(2.0*(1.0+nu)) + lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + jacobian = isotropic_elasticity_tensor(lambda, mu) + drde = zeros((19,6)) + drde[1:6, 1:6] = -tovoigt(jacobian) + @info "drde = $drde" + @info "size(drde) = $(size(drde))" + jacobian2 = (drdx\drde)[1:6, 1:6] + @info "jacobian = $(tovoigt(jacobian))" + @info "jacobian2 = $jacobian2" + jacobian3 = (drdx[1:6, 1:6] + drdx[1:6,7:end]*(drdx[7:end,7:end]\-drdx[7:end, 1:6]))\drde[1:6, 1:6] + @info "jacobian3 = $jacobian3" + @info "jacobian4 = $(tovoigt(chabmat.variables_new.jacobian))" + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" + + chabmat.ddrivers = ddrivers + integrate_material!(chabmat) + g! = create_nonlinear_system_of_equations(chabmat) + function residuals(x) + F = similar(x) + g!(F, x) + return F + end + + x0 = [tovoigt(chabmat.variables_new.stress); chabmat.variables_new.R; tovoigt(chabmat.variables_new.X1); tovoigt(chabmat.variables_new.X2)] + drdx = ForwardDiff.jacobian(residuals, x0) + @info "size(drdx) = $(size(drdx))" + @info "drdx = $drdx" + @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = parameters + mu = E/(2.0*(1.0+nu)) + lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + jacobian = isotropic_elasticity_tensor(lambda, mu) + drde = zeros((19,6)) + drde[1:6, 1:6] = -tovoigt(jacobian) + @info "drde = $drde" + @info "size(drde) = $(size(drde))" + jacobian2 = (drdx\drde)[1:6, 1:6] + @info "jacobian = $(tovoigt(jacobian))" + @info "jacobian2 = $jacobian2" + jacobian3 = (drdx[1:6, 1:6] + drdx[1:6,7:end]*(drdx[7:end,7:end]\-drdx[7:end, 1:6]))\drde[1:6, 1:6] + @info "jacobian3 = $jacobian3" + @info "jacobian4 = $(tovoigt(chabmat.variables_new.jacobian))" + update!(chabmat) + @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" +end +#simple_integration_test_fd_tangent2() + +end From 1d74bf57fcbfb8cb3a975e5a76e92baa32aaed89 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 26 May 2020 16:41:01 +0300 Subject: [PATCH 02/66] Chaboche material: clean up implementation, add comments --- src/chaboche.jl | 173 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 54 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 4f62114..149b0bf 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -1,9 +1,15 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE +# TODO: parametrize the short aliases on the element type? +typealias SymmT2 SymmetricTensor{2,3} +typealias SymmT4 SymmetricTensor{4,3} +typealias SymmT2Float64 SymmetricTensor{2,3,Float64} +typealias SymmT4Float64 SymmetricTensor{4,3,Float64} + @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + strain :: SymmT2 = zero(SymmT2Float64) end @with_kw struct ChabocheParameterState <: AbstractMaterialState @@ -21,13 +27,13 @@ end end @with_kw struct ChabocheVariableState <: AbstractMaterialState - stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - X1 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - X2 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + stress :: SymmT2 = zero(SymmT2Float64) + X1 :: SymmT2 = zero(SymmT2Float64) + X2 :: SymmT2 = zero(SymmT2Float64) + plastic_strain :: SymmT2 = zero(SymmT2Float64) cumeq :: Float64 = zero(Float64) R :: Float64 = zero(Float64) - jacobian :: SymmetricTensor{4,3} = zero(SymmetricTensor{4,3,Float64}) + jacobian :: SymmT4 = zero(SymmT4Float64) end @with_kw mutable struct Chaboche <: AbstractMaterial @@ -39,57 +45,100 @@ end dparameters :: ChabocheParameterState = ChabocheParameterState() end +# Convert the elastic constants (E, ν) to the Lamé constants (μ, λ). Isotropic material. +@inline function lame(E, ν) + μ = E/(2.0*(1.0+nu)) + λ = E*nu/((1.0+nu)*(1.0-2.0*nu)) + return μ, λ +end + +# Adaptors for NLsolve. Marshal the problem state into a Vector and back. +@inline function state_to_vector(σ::SymmT2Float64, + R::Float64, + X1::SymmT2Float64, + X2::SymmT2Float64) + return [tovoigt(σ), R, tovoigt(X1), tovoigt(X2)] +end +@inline function state_from_vector(x::Vector{Float64}) + σ = fromvoigt(SymmT2Float64, @view x[1:6]) + R = x[7] + X1 = fromvoigt(SymmT2Float64, @view x[8:13]) + X2 = fromvoigt(SymmT2Float64, @view x[14:19]) + return σ, R, X1, X2 +end + +# Take away the bang; i.e. produce an equivalent single-argument non-mutating +# function f (that allocates and returns the result), when given a two-argument +# mutating function f! (which writes its result into the first argument). +# +# The argument and result types of F! must be the same. +# +# TODO: To generalize, we could make a @generated function that reflects on the +# parameter types of f!, and creates `out` using the type of f!'s first argument +# (instead of using `x` as we do now). Of course we're still relying on the type having +# a sensible default constructor (to perform the allocation when similar() is called). +function debang(f!) + function f(x) + out = similar(x) + f!(out, x) + return out + end + return f +end + function integrate_material!(material::Chaboche) p = material.parameters v = material.variables dd = material.ddrivers d = material.drivers @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p - mu = E/(2.0*(1.0+nu)) - lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + mu, lambda = lame(E, nu) @unpack strain, time = d dstrain = dd.strain dtime = dd.time @unpack stress, X1, X2, plastic_strain, cumeq, R, jacobian = v - jacobian = isotropic_elasticity_tensor(lambda, mu) + # elastic part + jacobian = isotropic_elasticity_tensor(lambda, mu) # dσ/dε, i.e. ∂σij/∂εkl + stress += dcontract(jacobian, dstrain) # add the elastic stress increment, get the intermediate stress - stress += dcontract(jacobian, dstrain) - seff = stress - X1 - X2 - seff_dev = dev(seff) - f = sqrt(1.5)*norm(seff_dev) - (R0 + R) + # resulting deviatoric plastic stress (accounting for backstresses Xm) + seff_dev = dev(stress - X1 - X2) + # von Mises yield function; f := J(seff_dev) - Y + f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using intermediate problem state, after elastic update if f > 0.0 g! = create_nonlinear_system_of_equations(material) - x0 = [tovoigt(stress); R; tovoigt(X1); tovoigt(X2)] - F = similar(x0) - res = nlsolve(g!, x0; autodiff = :forward) + x0 = state_to_vector(stress, R, X1, X2) + res = nlsolve(g!, x0; autodiff = :forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl + converged(res) || error("Nonlinear system of equations did not converge!") x = res.zero - res.f_converged || error("Nonlinear system of equations did not converge!") - - stress = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[1:6]) - R = x[7] - X1 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[8:13]) - X2 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[14:19]) - seff = stress - X1 - X2 - seff_dev = dev(seff) - f = sqrt(1.5)*norm(seff_dev) - (R0 + R) - dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn - dp = dotp*dtime - n = sqrt(1.5)*seff_dev/norm(seff_dev) + stress, R, X1, X2 = state_from_vector(x) + + seff_dev = dev(stress - X1 - X2) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using new problem state + + dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn # plasticity multiplier, see equations (3) and (4) in Chaboche 2013 + dp = dotp*dtime # |dε_p|, using backward Euler (dotp is ∂ε_p/∂t at the end of the timestep) + n = sqrt(1.5)*seff_dev/norm(seff_dev) # Chaboche: a (tensorial) unit direction, s.t. 2/3 * (n : n) = 1 plastic_strain += dp*n - cumeq += dp - # Compute Jacobian - function residuals(x) - F = similar(x) - g!(F, x) - return F - end - drdx = ForwardDiff.jacobian(residuals, x) - drde = zeros((length(x),6)) - drde[1:6, 1:6] = -tovoigt(jacobian) - jacobian = fromvoigt(SymmetricTensor{4,3}, (drdx\drde)[1:6, 1:6]) + cumeq += dp # TODO: Some kind of 1D cumulative plastic strain? (Note dp ≥ 0.) What is this used for? + + # Compute the new Jacobian, accounting for the plastic contribution. Because + # x ≡ [σ R X1 X2] (vector of length 19, with tensors encoded in Voigt format) + # we have + # (dx/dε)[1:6,1:6] = dσ/dε + # for which we can compute the LHS as follows: + # dx/dε = dx/dr dr/dε = inv(dr/dx) dr/dε ≡ (dr/dx) \ (dr/dε) + # where r = r(x) is the residual, given by the function g!. AD can get us dr/dx automatically, + # the other factor we will have to supply manually. + drdx = ForwardDiff.jacobian(debang(g!), x) # Array{19, 19} + drde = zeros((length(x),6)) # Array{19, 6} + # We are only interested in dσ/dε, so the rest of drde can be left as zeros. + # TODO: where does the minus sign come from? + drde[1:6, 1:6] = -tovoigt(jacobian) # (negative of the) elastic Jacobian. Follows from the defn. of g!. + jacobian = fromvoigt(SymmT4, (drdx\drde)[1:6, 1:6]) end variables_new = ChabocheVariableState(stress = stress, X1 = X1, @@ -107,38 +156,54 @@ function create_nonlinear_system_of_equations(material::Chaboche) dd = material.ddrivers d = material.drivers @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p - mu = E/(2.0*(1.0+nu)) - lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + mu, lambda = lame(E, nu) + # Store the old problem state (i.e. the problem state at the time when this + # equation system instance was created). @unpack strain, time = d dstrain = dd.strain dtime = dd.time @unpack stress, X1, X2, plastic_strain, cumeq, R = v - function g!(F, x::Vector{T}) where {T} # System of non-linear equations - jacobian = isotropic_elasticity_tensor(lambda, mu) - stress_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[1:6]) - R_ = x[7] - X1_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[8:13]) - X2_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[14:19]) + # Compute the residual. F is output, x is filled by NLsolve. + # The solution is x = x* such that g(x*) = 0. + function g!(F, x::Vector{T}) where {T} + jacobian = isotropic_elasticity_tensor(lambda, mu) # TODO: why not compute once, store in closure? + stress_, R_, X1_, X2_ = state_from_vector(x) # tentative new values from nlsolve - seff = stress_ - X1_ - X2_ - seff_dev = dev(seff) + seff_dev = dev(stress_ - X1_ - X2_) f = sqrt(1.5)*norm(seff_dev) - (R0 + R_) dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn dp = dotp*dtime n = sqrt(1.5)*seff_dev/norm(seff_dev) + + # The equations are written in a delta form: + # + # Δσ = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) + # ΔR = b (Q - R_new) |dε| (component 7) + # ΔX1 = (2/3) C1 |dε| (n - (3/2) (D1/C1) X1_new) (components 8:13) + # ΔX2 = (2/3) C2 |dε| (n - (3/2) (D2/C2) X2_new) (components 14:19) + # + # where + # + # Δ(...) = (...)_new - (...)_old + # + # Then move the delta terms to the RHS to get the standard form, (stuff) = 0. + # dstrain_plastic = dp*n - tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain - dstrain_plastic)) - F[7] = R - R_ + b*(Q-R_)*dp + dstrain_elastic = dstrain - dstrain_plastic + tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain_elastic)) + F[7] = R - R_ + b*(Q - R_)*dp if isapprox(C1, 0.0) - tovoigt!(view(F,8:13),X1 - X1_) + # TODO: In order for the below general case to reduce to this, both C1 **and D1** must be zero. + tovoigt!(view(F, 8:13), X1 - X1_) else - tovoigt!(view(F,8:13), X1 - X1_ + 2.0/3.0*C1*dp*(n - 1.5*D1/C1*X1_)) + tovoigt!(view(F, 8:13), X1 - X1_ + 2.0/3.0*C1*dp*(n - 1.5*D1/C1*X1_)) end if isapprox(C2, 0.0) - tovoigt!(view(F,14:19), X2 - X2_) + # TODO: In order for the below general case to reduce to this, both C2 **and D2** must be zero. + tovoigt!(view(F, 14:19), X2 - X2_) else tovoigt!(view(F, 14:19), X2 - X2_ + 2.0/3.0*C2*dp*(n - 1.5*D2/C2*X2_)) end From 8352fe7862e8d0691781a363e38d94a21f00a20c Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 1 Jun 2020 14:38:35 +0300 Subject: [PATCH 03/66] Cleanup vol. 2 --- src/chaboche.jl | 145 ++++++++++++++++++++++++++++++------------------ 1 file changed, 90 insertions(+), 55 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 149b0bf..39009df 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -1,15 +1,12 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE -# TODO: parametrize the short aliases on the element type? -typealias SymmT2 SymmetricTensor{2,3} -typealias SymmT4 SymmetricTensor{4,3} -typealias SymmT2Float64 SymmetricTensor{2,3,Float64} -typealias SymmT4Float64 SymmetricTensor{4,3,Float64} +const Symm2{T} = SymmetricTensor{2,3,T} +const Symm4{T} = SymmetricTensor{4,3,T} @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmT2 = zero(SymmT2Float64) + strain :: Symm2 = zero(Symm2{Float64}) end @with_kw struct ChabocheParameterState <: AbstractMaterialState @@ -27,13 +24,13 @@ end end @with_kw struct ChabocheVariableState <: AbstractMaterialState - stress :: SymmT2 = zero(SymmT2Float64) - X1 :: SymmT2 = zero(SymmT2Float64) - X2 :: SymmT2 = zero(SymmT2Float64) - plastic_strain :: SymmT2 = zero(SymmT2Float64) + stress :: Symm2 = zero(Symm2{Float64}) + X1 :: Symm2 = zero(Symm2{Float64}) + X2 :: Symm2 = zero(Symm2{Float64}) + plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) R :: Float64 = zero(Float64) - jacobian :: SymmT4 = zero(SymmT4Float64) + jacobian :: Symm4 = zero(Symm4{Float64}) end @with_kw mutable struct Chaboche <: AbstractMaterial @@ -46,46 +43,88 @@ end end # Convert the elastic constants (E, ν) to the Lamé constants (μ, λ). Isotropic material. -@inline function lame(E, ν) +@inline function lame(E::Real, ν::Real) μ = E/(2.0*(1.0+nu)) λ = E*nu/((1.0+nu)*(1.0-2.0*nu)) return μ, λ end # Adaptors for NLsolve. Marshal the problem state into a Vector and back. -@inline function state_to_vector(σ::SymmT2Float64, - R::Float64, - X1::SymmT2Float64, - X2::SymmT2Float64) +@inline function state_to_vector(σ::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real return [tovoigt(σ), R, tovoigt(X1), tovoigt(X2)] end -@inline function state_from_vector(x::Vector{Float64}) - σ = fromvoigt(SymmT2Float64, @view x[1:6]) +@inline function state_from_vector(x::AbstractVector{Real}) + σ = fromvoigt(Symm2{S}, @view x[1:6]) R = x[7] - X1 = fromvoigt(SymmT2Float64, @view x[8:13]) - X2 = fromvoigt(SymmT2Float64, @view x[14:19]) + X1 = fromvoigt(Symm2{S}, @view x[8:13]) + X2 = fromvoigt(Symm2{S}, @view x[14:19]) return σ, R, X1, X2 end +# f!(F, x) -> f(x) +# # Take away the bang; i.e. produce an equivalent single-argument non-mutating # function f (that allocates and returns the result), when given a two-argument # mutating function f! (which writes its result into the first argument). # -# The argument and result types of F! must be the same. +# When the types, sizes and shapes of the output F are the same as those for the +# input x, it is enough to supply just f!. +# +# When the output type, size and/or shape is different from the input, then an +# example instance of the correct type with the correct size and shape for the +# output must be supplied, as the ex argument. It is only used to allocate +# uninitialized memory with the right type, size and shape whenever f is called. # -# TODO: To generalize, we could make a @generated function that reflects on the -# parameter types of f!, and creates `out` using the type of f!'s first argument -# (instead of using `x` as we do now). Of course we're still relying on the type having -# a sensible default constructor (to perform the allocation when similar() is called). -function debang(f!) - function f(x) - out = similar(x) - f!(out, x) - return out +# (While the type of F is known at compile time, the size and shape are +# typically runtime properties, not encoded into the type. For example, arrays +# have the number of dimensions encoded into the type, but the length of each +# dimension is defined at run time, when an instance is created.) +# +function debang(f!, ex=nothing) + if ex === nothing + function f(x) + out = similar(x) + f!(out, x) + return out + end + else + function f(x) + out = similar(ex) + f!(out, x) + return out + end end return f end +# function debang(f!) +# # Adapted from +# # https://white.ucc.asn.au/2018/10/03/Dispatch,-Traits-and-Metaprogramming-Over-Reflection.html +# arg1type(f::Function) = map(arg1type, methods(f)) # check each method of function +# arg1type(m::Method) = arg1type(m.sig) +# arg1type(x::Any) = throw(x, ArgumentError("unsupported argument type")) +# arg1type(::Type{Tuple{F, T1, VarArg{Any}}}) where {F, T1} = T1 +# +# arg2type(f::Function) = map(arg2type, methods(f)) # check each method of function +# arg2type(m::Method) = arg2type(m.sig) +# arg2type(x::Any) = throw(x, ArgumentError("unsupported argument type")) +# arg2type(::Type{Tuple{F, T1, T2, VarArg{Any}}}) where {F, T1, T2} = T2 +# +# mm = collect(methods(f!)) +# if length(mm) > 1 +# throw(ArgumentError(f!, "debang is only implemented for functions with a single method")) +# end +# themethod = mm[1] +# +# T = arg1type(themethod) +# function f(x) +# out = similar(T) # can't work, required information not present in T alone. +# f!(out, x) +# return out +# end +# return f +# end + function integrate_material!(material::Chaboche) p = material.parameters v = material.variables @@ -97,16 +136,16 @@ function integrate_material!(material::Chaboche) @unpack strain, time = d dstrain = dd.strain dtime = dd.time - @unpack stress, X1, X2, plastic_strain, cumeq, R, jacobian = v + @unpack stress, X1, X2, plastic_strain, cumeq, R = v # elastic part jacobian = isotropic_elasticity_tensor(lambda, mu) # dσ/dε, i.e. ∂σij/∂εkl - stress += dcontract(jacobian, dstrain) # add the elastic stress increment, get the intermediate stress + stress += dcontract(jacobian, dstrain) # add the elastic stress increment, get the elastic trial stress # resulting deviatoric plastic stress (accounting for backstresses Xm) seff_dev = dev(stress - X1 - X2) # von Mises yield function; f := J(seff_dev) - Y - f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using intermediate problem state, after elastic update + f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using elastic trial problem state if f > 0.0 g! = create_nonlinear_system_of_equations(material) x0 = state_to_vector(stress, R, X1, X2) @@ -115,15 +154,16 @@ function integrate_material!(material::Chaboche) x = res.zero stress, R, X1, X2 = state_from_vector(x) + # using the new problem state seff_dev = dev(stress - X1 - X2) - f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using new problem state + f = sqrt(1.5)*norm(seff_dev) - (R0 + R) dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn # plasticity multiplier, see equations (3) and (4) in Chaboche 2013 dp = dotp*dtime # |dε_p|, using backward Euler (dotp is ∂ε_p/∂t at the end of the timestep) n = sqrt(1.5)*seff_dev/norm(seff_dev) # Chaboche: a (tensorial) unit direction, s.t. 2/3 * (n : n) = 1 plastic_strain += dp*n - cumeq += dp # TODO: Some kind of 1D cumulative plastic strain? (Note dp ≥ 0.) What is this used for? + cumeq += dp # cumulative equivalent plastic strain (note dp ≥ 0) # Compute the new Jacobian, accounting for the plastic contribution. Because # x ≡ [σ R X1 X2] (vector of length 19, with tensors encoded in Voigt format) @@ -138,7 +178,7 @@ function integrate_material!(material::Chaboche) # We are only interested in dσ/dε, so the rest of drde can be left as zeros. # TODO: where does the minus sign come from? drde[1:6, 1:6] = -tovoigt(jacobian) # (negative of the) elastic Jacobian. Follows from the defn. of g!. - jacobian = fromvoigt(SymmT4, (drdx\drde)[1:6, 1:6]) + jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end variables_new = ChabocheVariableState(stress = stress, X1 = X1, @@ -158,17 +198,21 @@ function create_nonlinear_system_of_equations(material::Chaboche) @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p mu, lambda = lame(E, nu) - # Store the old problem state (i.e. the problem state at the time when this - # equation system instance was created). + # Old problem state (i.e. the problem state at the time when this equation + # system instance was created). + # + # Note this does not include the elastic trial; this is the state at the + # end of the previous timestep. @unpack strain, time = d dstrain = dd.strain dtime = dd.time @unpack stress, X1, X2, plastic_strain, cumeq, R = v + jacobian = isotropic_elasticity_tensor(lambda, mu) + # Compute the residual. F is output, x is filled by NLsolve. # The solution is x = x* such that g(x*) = 0. - function g!(F, x::Vector{T}) where {T} - jacobian = isotropic_elasticity_tensor(lambda, mu) # TODO: why not compute once, store in closure? + function g!(F::V, x::V) where V <: AbstractVector{Real} stress_, R_, X1_, X2_ = state_from_vector(x) # tentative new values from nlsolve seff_dev = dev(stress_ - X1_ - X2_) @@ -181,9 +225,9 @@ function create_nonlinear_system_of_equations(material::Chaboche) # The equations are written in a delta form: # # Δσ = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) - # ΔR = b (Q - R_new) |dε| (component 7) - # ΔX1 = (2/3) C1 |dε| (n - (3/2) (D1/C1) X1_new) (components 8:13) - # ΔX2 = (2/3) C2 |dε| (n - (3/2) (D2/C2) X2_new) (components 14:19) + # ΔR = b (Q - R_new) |dε_p| (component 7) + # ΔX1 = (2/3) C1 |dε_p| (n - (3/2) (D1/C1) X1_new) (components 8:13) + # ΔX2 = (2/3) C2 |dε_p| (n - (3/2) (D2/C2) X2_new) (components 14:19) # # where # @@ -195,18 +239,9 @@ function create_nonlinear_system_of_equations(material::Chaboche) dstrain_elastic = dstrain - dstrain_plastic tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain_elastic)) F[7] = R - R_ + b*(Q - R_)*dp - if isapprox(C1, 0.0) - # TODO: In order for the below general case to reduce to this, both C1 **and D1** must be zero. - tovoigt!(view(F, 8:13), X1 - X1_) - else - tovoigt!(view(F, 8:13), X1 - X1_ + 2.0/3.0*C1*dp*(n - 1.5*D1/C1*X1_)) - end - if isapprox(C2, 0.0) - # TODO: In order for the below general case to reduce to this, both C2 **and D2** must be zero. - tovoigt!(view(F, 14:19), X2 - X2_) - else - tovoigt!(view(F, 14:19), X2 - X2_ + 2.0/3.0*C2*dp*(n - 1.5*D2/C2*X2_)) - end + # dp is a scalar, so it commutes in multiplication. This allows us to avoid the division by C1. + tovoigt!(view(F, 8:13), X1 + (-1.0 + dp*(2.0/3.0*C1*n - D1*X1_))) + tovoigt!(view(F, 14:19), X2 + (-1.0 + dp*(2.0/3.0*C2*n - D2*X2_))) end return g! end From 94c93b54ab3acd5ba89a9c417de39941184c1e7a Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 2 Jun 2020 13:34:36 +0300 Subject: [PATCH 04/66] More cleanup --- src/chaboche.jl | 142 +++++++++++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 50 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 39009df..892452c 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -1,7 +1,10 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE +"""Alias for symmetric tensor type of rank 2, dimension 3.""" const Symm2{T} = SymmetricTensor{2,3,T} + +"""Alias for symmetric tensor type of rank 4, dimension 3.""" const Symm4{T} = SymmetricTensor{4,3,T} @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState @@ -42,17 +45,42 @@ end dparameters :: ChabocheParameterState = ChabocheParameterState() end -# Convert the elastic constants (E, ν) to the Lamé constants (μ, λ). Isotropic material. +""" + lame(E, ν) + +Convert the elastic parameters (E, ν) to the Lamé parameters (μ, λ). Isotropic material. +""" @inline function lame(E::Real, ν::Real) - μ = E/(2.0*(1.0+nu)) - λ = E*nu/((1.0+nu)*(1.0-2.0*nu)) + μ = E / (2.0 * (1.0 + nu)) + λ = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) return μ, λ end -# Adaptors for NLsolve. Marshal the problem state into a Vector and back. +""" + delame(E, ν) + +Convert the Lamé parameters (μ, λ) to the elastic parameters (E, ν). Isotropic material. +""" +@inline function delame(μ::Real, λ::Real) + E = μ * (3.0 * λ + 2.0 * μ) / (λ + μ) + ν = λ / (2.0 * (λ + μ)) + return E, ν +end + +""" + state_to_vector(σ::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real + +Marshal the problem state into a `Vector`. Adaptor for `nlsolve`. +""" @inline function state_to_vector(σ::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real return [tovoigt(σ), R, tovoigt(X1), tovoigt(X2)] end + +""" + state_from_vector(x::AbstractVector{Real}) + +Unmarshal the problem state from a `Vector`. Adaptor for `nlsolve`. +""" @inline function state_from_vector(x::AbstractVector{Real}) σ = fromvoigt(Symm2{S}, @view x[1:6]) R = x[7] @@ -61,25 +89,37 @@ end return σ, R, X1, X2 end -# f!(F, x) -> f(x) -# -# Take away the bang; i.e. produce an equivalent single-argument non-mutating -# function f (that allocates and returns the result), when given a two-argument -# mutating function f! (which writes its result into the first argument). -# -# When the types, sizes and shapes of the output F are the same as those for the -# input x, it is enough to supply just f!. -# -# When the output type, size and/or shape is different from the input, then an -# example instance of the correct type with the correct size and shape for the -# output must be supplied, as the ex argument. It is only used to allocate -# uninitialized memory with the right type, size and shape whenever f is called. -# -# (While the type of F is known at compile time, the size and shape are -# typically runtime properties, not encoded into the type. For example, arrays -# have the number of dimensions encoded into the type, but the length of each -# dimension is defined at run time, when an instance is created.) -# +""" + debang(f!, ex=nothing) + +Take away the bang; i.e. convert a mutating function into non-mutating form. + +`f!` must be a two-argument mutating function, which writes the result into its +first argument. The result of `debang` is then `f`, a single-argument +non-mutating function that allocates and returns the result. Schematically: + +```julia + f!(out, x) -> f(x) +``` + +When the type, size and shape of `out` is the same as those of `x`, it is enough +to supply just `f!`. When `f` is called, output will be allocated as `similar(x)`. + +When the type, size and/or shape of `out` are different from those of `x`, then +an example instance of the correct type with the correct size and shape for the +output must be supplied, as debang's `ex` argument. When `f` is called, output +will be allocated as `similar(ex)`. + +(While the type of F is known at compile time, the size and shape are typically +runtime properties, not encoded into the type. For example, arrays have the +number of dimensions encoded into the type, but the length of each dimension +is defined at run time, when an instance is created.) + +# Etymology + +By convention, mutating functions are marked with an exclamation mark, a.k.a. +bang. Debanging takes away the bang. +""" function debang(f!, ex=nothing) if ex === nothing function f(x) @@ -97,34 +137,12 @@ function debang(f!, ex=nothing) return f end -# function debang(f!) -# # Adapted from -# # https://white.ucc.asn.au/2018/10/03/Dispatch,-Traits-and-Metaprogramming-Over-Reflection.html -# arg1type(f::Function) = map(arg1type, methods(f)) # check each method of function -# arg1type(m::Method) = arg1type(m.sig) -# arg1type(x::Any) = throw(x, ArgumentError("unsupported argument type")) -# arg1type(::Type{Tuple{F, T1, VarArg{Any}}}) where {F, T1} = T1 -# -# arg2type(f::Function) = map(arg2type, methods(f)) # check each method of function -# arg2type(m::Method) = arg2type(m.sig) -# arg2type(x::Any) = throw(x, ArgumentError("unsupported argument type")) -# arg2type(::Type{Tuple{F, T1, T2, VarArg{Any}}}) where {F, T1, T2} = T2 -# -# mm = collect(methods(f!)) -# if length(mm) > 1 -# throw(ArgumentError(f!, "debang is only implemented for functions with a single method")) -# end -# themethod = mm[1] -# -# T = arg1type(themethod) -# function f(x) -# out = similar(T) # can't work, required information not present in T alone. -# f!(out, x) -# return out -# end -# return f -# end +""" + integrate_material!(material::Chaboche) +Integrate one timestep. The input `material` represents the problem state at the +end of the previous timestep. +""" function integrate_material!(material::Chaboche) p = material.parameters v = material.variables @@ -190,6 +208,30 @@ function integrate_material!(material::Chaboche) material.variables_new = variables_new end +""" + create_nonlinear_system_of_equations(material::Chaboche) + +Create and return an instance of the equation system for the delta form of the +evolution equations. + +The input `material` represents the problem state at the end of the previous +timestep. The created equation system will hold its own copy of that state. + +The equation system is represented as a mutating function that computes the +residual: + +```julia + g!(F::V, x::V) where V <: AbstractVector{Real} +``` + +Both `F` (output) and `x` (input) are length-19 vectors containing +[σ, R, X1, X2], in that order. The tensor quantities σ, X1, X2 are +encoded in Voigt format. + +The function `g!` is intended to be handed over to `nlsolve`. + +Used internally for computing the plastic contribution in `integrate_material!`. +""" function create_nonlinear_system_of_equations(material::Chaboche) p = material.parameters v = material.variables From 7bb5509155dde7337a9294da2c83d27cf59d330b Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 2 Jun 2020 13:53:37 +0300 Subject: [PATCH 05/66] Docstrings --- src/Materials.jl | 39 ++++++++++++++++++++++++++++++++++++++- src/chaboche.jl | 14 ++++---------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/Materials.jl b/src/Materials.jl index 4a0b79c..d606d05 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -8,6 +8,15 @@ using LinearAlgebra, ForwardDiff, Tensors, NLsolve, Parameters abstract type AbstractMaterial end abstract type AbstractMaterialState end +""" + :+(state::T, dstate::T) where {T <: AbstractMaterialState} + +Addition for material states. + +Given two material states `state` and `dstate` of type `T`, add each field of +`dstate` into the corresponding field of `state`. Return the resulting material +state. +""" @generated function Base.:+(state::T, dstate::T) where {T <: AbstractMaterialState} expr = [:(state.$p + dstate.$p) for p in fieldnames(T)] return :(T($(expr...))) @@ -15,10 +24,25 @@ end export AbstractMaterial, AbstractMaterialState +""" + integrate_material!(material::M) where {M<:AbstractMaterial} + +Integrate one timestep. The input `material` represents the problem state at the +end of the previous timestep. + +Abstract method. Must be implemented for each material type. When integration is +done, the method must update the `material` argument to have the new state. +""" function integrate_material!(material::M) where {M<:AbstractMaterial} error("One needs to define how to integrate material $M!") end +""" + update_material!(material::M) where {M <: AbstractMaterial} + +In `material`, add `ddrivers` into `drivers`, `dparameters` into `parameters`, +and replace `variables` by `variables_new`. Then `reset_material!`. +""" function update_material!(material::M) where {M <: AbstractMaterial} material.drivers += material.ddrivers material.parameters += material.dparameters @@ -27,6 +51,13 @@ function update_material!(material::M) where {M <: AbstractMaterial} return nothing end +""" + reset_material!(material::M) where {M <: AbstractMaterial} + +In `material`, zero out `ddrivers`, `dparameters` and `variables_new`. + +Used internally by `update_material!`. +""" function reset_material!(material::M) where {M <: AbstractMaterial} material.ddrivers = typeof(material.ddrivers)() material.dparameters = typeof(material.dparameters)() @@ -36,9 +67,15 @@ end export integrate_material!, update_material!, reset_material! +""" + isotropic_elasticity_tensor(lambda, mu) + +Compute the elasticity tensor (rank 4, symmetric) for an isotropic material +having the Lamé parameters `lambda` and `mu`. +""" function isotropic_elasticity_tensor(lambda, mu) delta(i,j) = i==j ? 1.0 : 0.0 - g(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k)) + g(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k)) jacobian = SymmetricTensor{4, 3, Float64}(g) return jacobian end diff --git a/src/chaboche.jl b/src/chaboche.jl index 892452c..78c6851 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -137,12 +137,6 @@ function debang(f!, ex=nothing) return f end -""" - integrate_material!(material::Chaboche) - -Integrate one timestep. The input `material` represents the problem state at the -end of the previous timestep. -""" function integrate_material!(material::Chaboche) p = material.parameters v = material.variables @@ -212,12 +206,14 @@ end create_nonlinear_system_of_equations(material::Chaboche) Create and return an instance of the equation system for the delta form of the -evolution equations. +evolution equations of the Chaboche material. + +Used internally for computing the plastic contribution in `integrate_material!`. The input `material` represents the problem state at the end of the previous timestep. The created equation system will hold its own copy of that state. -The equation system is represented as a mutating function that computes the +The equation system is represented as a mutating function `g!` that computes the residual: ```julia @@ -229,8 +225,6 @@ Both `F` (output) and `x` (input) are length-19 vectors containing encoded in Voigt format. The function `g!` is intended to be handed over to `nlsolve`. - -Used internally for computing the plastic contribution in `integrate_material!`. """ function create_nonlinear_system_of_equations(material::Chaboche) p = material.parameters From 85d8f8d1e255b99efa71e283f6b99fbf9d019976 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 2 Jun 2020 14:18:23 +0300 Subject: [PATCH 06/66] More cleanup --- examples/abstractmaterial_full_workflow.jl | 42 +++++----- examples/abstractmaterial_full_workflow2.jl | 42 +++++----- examples/abstractmaterial_testing.jl | 24 +++--- examples/abstractmaterial_testing_states.jl | 30 +++---- src/Materials.jl | 14 +--- src/MaterialsUtils.jl | 92 +++++++++++++++++++++ src/biaxial_increment.jl | 2 +- src/chaboche.jl | 80 +----------------- src/idealplastic.jl | 12 +-- src/stress_driven_uniaxial_increment.jl | 2 +- src/uniaxial_increment.jl | 2 +- test/test_chaboche.jl | 2 +- test/test_chaboche_shear.jl | 2 +- test/test_idealplastic.jl | 20 ++--- test/test_idealplastic_shear.jl | 2 +- 15 files changed, 187 insertions(+), 181 deletions(-) create mode 100644 src/MaterialsUtils.jl diff --git a/examples/abstractmaterial_full_workflow.jl b/examples/abstractmaterial_full_workflow.jl index c6214fc..88185d7 100644 --- a/examples/abstractmaterial_full_workflow.jl +++ b/examples/abstractmaterial_full_workflow.jl @@ -14,7 +14,7 @@ end @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + strain :: Symm2 = zero(Symm2{Float64}) end @with_kw struct ChabocheParameterState <: AbstractMaterialState @@ -32,13 +32,13 @@ end end @with_kw struct ChabocheVariableState <: AbstractMaterialState - stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - X1 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - X2 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + stress :: Symm2 = zero(Symm2{Float64}) + X1 :: Symm2 = zero(Symm2{Float64}) + X2 :: Symm2 = zero(Symm2{Float64}) + plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) R :: Float64 = zero(Float64) - jacobian :: SymmetricTensor{4,3} = zero(SymmetricTensor{4,3,Float64}) + jacobian :: Symm4 = zero(Symm4{Float64}) end @with_kw mutable struct Chaboche <: AbstractMaterial @@ -52,7 +52,7 @@ end @with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + strain :: Symm2 = zero(Symm2{Float64}) end @with_kw struct IdealPlasticParameterState <: AbstractMaterialState @@ -62,8 +62,8 @@ end end @with_kw struct IdealPlasticVariableState <: AbstractMaterialState - stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + stress :: Symm2 = zero(Symm2{Float64}) + plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) end @@ -108,7 +108,7 @@ mat2 = IdealPlastic() function isotropic_elasticity_tensor(lambda, mu) delta(i,j) = i==j ? 1.0 : 0.0 g(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k)) - jacobian = SymmetricTensor{4, 3, Float64}(g) + jacobian = Symm4{Float64}(g) return jacobian end @@ -140,10 +140,10 @@ function integrate_material!(material::Chaboche) x = res.zero res.f_converged || error("Nonlinear system of equations did not converge!") - stress = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[1:6]) + stress = fromvoigt(Symm2{Float64}, @view x[1:6]) R = x[7] - X1 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[8:13]) - X2 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[14:19]) + X1 = fromvoigt(Symm2{Float64}, @view x[8:13]) + X2 = fromvoigt(Symm2{Float64}, @view x[14:19]) seff = stress - X1 - X2 seff_dev = dev(seff) f = sqrt(1.5)*norm(seff_dev) - (R0 + R) @@ -162,7 +162,7 @@ function integrate_material!(material::Chaboche) drdx = ForwardDiff.jacobian(residuals, x) drde = zeros((length(x),6)) drde[1:6, 1:6] = -tovoigt(jacobian) - jacobian = fromvoigt(SymmetricTensor{4,3}, (drdx\drde)[1:6, 1:6]) + jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end variables_new = ChabocheVariableState(stress = stress, X1 = X1, @@ -190,10 +190,10 @@ function create_nonlinear_system_of_equations(material::Chaboche) function g!(F, x::Vector{T}) where {T} # System of non-linear equations jacobian = isotropic_elasticity_tensor(lambda, mu) - stress_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[1:6]) + stress_ = fromvoigt(Symm2{T}, @view x[1:6]) R_ = x[7] - X1_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[8:13]) - X2_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[14:19]) + X1_ = fromvoigt(Symm2{T}, @view x[8:13]) + X2_ = fromvoigt(Symm2{T}, @view x[14:19]) seff = stress_ - X1_ - X2_ seff_dev = dev(seff) @@ -232,7 +232,7 @@ function simple_integration_test() Q = 50.0, b = 0.1) - dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" @@ -286,7 +286,7 @@ function test_chaboche() s33s = [chabmat.variables.stress[3,3]] for i=2:length(ts) dtime = ts[i]-ts[i-1] - dstrain = fromvoigt(SymmetricTensor{2,3,Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) + dstrain = fromvoigt(Symm2{Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) chabmat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) integrate_material!(chabmat) update!(chabmat) @@ -314,7 +314,7 @@ function simple_integration_test_fd_tangent() Q = 50.0, b = 0.1) - dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) @@ -394,7 +394,7 @@ function simple_integration_test_fd_tangent2() Q = 50.0, b = 0.1) - dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) integrate_material!(chabmat) diff --git a/examples/abstractmaterial_full_workflow2.jl b/examples/abstractmaterial_full_workflow2.jl index b4356d4..e279726 100644 --- a/examples/abstractmaterial_full_workflow2.jl +++ b/examples/abstractmaterial_full_workflow2.jl @@ -17,7 +17,7 @@ end @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + strain :: Symm2 = zero(Symm2{Float64}) end @with_kw struct ChabocheParameterState <: AbstractMaterialState @@ -35,13 +35,13 @@ end end @with_kw struct ChabocheVariableState <: AbstractMaterialState - stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - X1 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - X2 :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + stress :: Symm2 = zero(Symm2{Float64}) + X1 :: Symm2 = zero(Symm2{Float64}) + X2 :: Symm2 = zero(Symm2{Float64}) + plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) R :: Float64 = zero(Float64) - jacobian :: SymmetricTensor{4,3} = zero(SymmetricTensor{4,3,Float64}) + jacobian :: Symm4 = zero(Symm4{Float64}) end @with_kw mutable struct Chaboche <: AbstractMaterial @@ -55,7 +55,7 @@ end @with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + strain :: Symm2 = zero(Symm2{Float64}) end @with_kw struct IdealPlasticParameterState <: AbstractMaterialState @@ -65,8 +65,8 @@ end end @with_kw struct IdealPlasticVariableState <: AbstractMaterialState - stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + stress :: Symm2 = zero(Symm2{Float64}) + plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) end @@ -111,7 +111,7 @@ mat2 = IdealPlastic() function isotropic_elasticity_tensor(lambda, mu) delta(i,j) = i==j ? 1.0 : 0.0 g(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k)) - jacobian = SymmetricTensor{4, 3, Float64}(g) + jacobian = Symm4{Float64}(g) return jacobian end @@ -143,10 +143,10 @@ function integrate_material!(material::Chaboche) x = res.zero res.f_converged || error("Nonlinear system of equations did not converge!") - stress = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[1:6]) + stress = fromvoigt(Symm2{Float64}, @view x[1:6]) R = x[7] - X1 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[8:13]) - X2 = fromvoigt(SymmetricTensor{2,3,Float64}, @view x[14:19]) + X1 = fromvoigt(Symm2{Float64}, @view x[8:13]) + X2 = fromvoigt(Symm2{Float64}, @view x[14:19]) seff = stress - X1 - X2 seff_dev = dev(seff) f = sqrt(1.5)*norm(seff_dev) - (R0 + R) @@ -165,7 +165,7 @@ function integrate_material!(material::Chaboche) drdx = ForwardDiff.jacobian(residuals, x) drde = zeros((length(x),6)) drde[1:6, 1:6] = -tovoigt(jacobian) - jacobian = fromvoigt(SymmetricTensor{4,3}, (drdx\drde)[1:6, 1:6]) + jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end variables_new = ChabocheVariableState(stress = stress, X1 = X1, @@ -193,10 +193,10 @@ function create_nonlinear_system_of_equations(material::Chaboche) function g!(F, x::Vector{T}) where {T} # System of non-linear equations jacobian = isotropic_elasticity_tensor(lambda, mu) - stress_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[1:6]) + stress_ = fromvoigt(Symm2{T}, @view x[1:6]) R_ = x[7] - X1_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[8:13]) - X2_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[14:19]) + X1_ = fromvoigt(Symm2{T}, @view x[8:13]) + X2_ = fromvoigt(Symm2{T}, @view x[14:19]) seff = stress_ - X1_ - X2_ seff_dev = dev(seff) @@ -235,7 +235,7 @@ function simple_integration_test() Q = 50.0, b = 0.1) - dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) @info "time = $(chabmat.drivers.time), stress = $(chabmat.variables.stress)" @@ -289,7 +289,7 @@ function test_chaboche() s33s = [chabmat.variables.stress[3,3]] for i=2:length(ts) dtime = ts[i]-ts[i-1] - dstrain = fromvoigt(SymmetricTensor{2,3,Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) + dstrain = fromvoigt(Symm2{Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) chabmat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) integrate_material!(chabmat) update!(chabmat) @@ -320,7 +320,7 @@ function simple_integration_test_fd_tangent() Q = 50.0, b = 0.1) - dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) @@ -400,7 +400,7 @@ function simple_integration_test_fd_tangent2() Q = 50.0, b = 0.1) - dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = ChabocheDriverState(time = 0.25, strain = 0.25*dstrain_dtime) chabmat = Chaboche(parameters = parameters, ddrivers = ddrivers) integrate_material!(chabmat) diff --git a/examples/abstractmaterial_testing.jl b/examples/abstractmaterial_testing.jl index 004b378..7d5bbe4 100644 --- a/examples/abstractmaterial_testing.jl +++ b/examples/abstractmaterial_testing.jl @@ -67,9 +67,9 @@ function bench_tensor() end function bench_symtensor() # Random walk test - var = Variable(SymmetricTensor{2, 3}([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) + var = Variable(Symm2([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) for i in 1:N - var.dvalue += randn(SymmetricTensor{2,3,Float64}) + var.dvalue += randn(Symm2{Float64}) update!(var) end end @@ -106,21 +106,21 @@ function update!(state::T) where {T<:AbstractVariableState} end function bench_chaboche_style_variablestate() - stress = Variable(zero(SymmetricTensor{2,3})) - strain = Variable(zero(SymmetricTensor{2,3})) - backstress1 = Variable(zero(SymmetricTensor{2,3})) - backstress2 = Variable(zero(SymmetricTensor{2,3})) - plastic_strain = Variable(zero(SymmetricTensor{2,3})) + stress = Variable(zero(Symm2)) + strain = Variable(zero(Symm2)) + backstress1 = Variable(zero(Symm2)) + backstress2 = Variable(zero(Symm2)) + plastic_strain = Variable(zero(Symm2)) cumeq = Variable(0.0) R = Variable(0.0) state = VariableState(stress, strain, backstress1, backstress2, plastic_strain, cumeq, R) for i in 1:N - state.stress.dvalue = randn(SymmetricTensor{2,3}) - state.strain.dvalue = randn(SymmetricTensor{2,3}) - state.backstress1.dvalue = randn(SymmetricTensor{2,3}) - state.backstress2.dvalue = randn(SymmetricTensor{2,3}) - state.plastic_strain.dvalue = randn(SymmetricTensor{2,3}) + state.stress.dvalue = randn(Symm2) + state.strain.dvalue = randn(Symm2) + state.backstress1.dvalue = randn(Symm2) + state.backstress2.dvalue = randn(Symm2) + state.plastic_strain.dvalue = randn(Symm2) state.cumeq.dvalue = norm(state.plastic_strain.dvalue) state.R.dvalue = randn() update!(state) diff --git a/examples/abstractmaterial_testing_states.jl b/examples/abstractmaterial_testing_states.jl index 959ee3b..473c4e0 100644 --- a/examples/abstractmaterial_testing_states.jl +++ b/examples/abstractmaterial_testing_states.jl @@ -9,23 +9,23 @@ abstract type AbstractMaterialState end end struct SomeState <: AbstractMaterialState - stress::SymmetricTensor{2,3,Float64} + stress::Symm2{Float64} end -state = SomeState(SymmetricTensor{2,3,Float64}([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) +state = SomeState(Symm2{Float64}([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) N = 1000 function bench_state(N) - state = SomeState(SymmetricTensor{2,3,Float64}([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) + state = SomeState(Symm2{Float64}([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) for i in 1:N - dstate = SomeState(randn(SymmetricTensor{2,3,Float64})) + dstate = SomeState(randn(Symm2{Float64})) state = state + dstate end return state end -# println("Benchmark State{SymmetricTensor{2,3,Float64}}") +# println("Benchmark State{Symm2{Float64}}") # @btime bench_state(N) struct AnotherState <: AbstractMaterialState @@ -38,21 +38,21 @@ struct AnotherState <: AbstractMaterialState R::Float64 end function bench_chaboche_style_state(N) - stress = zero(SymmetricTensor{2,3}) - strain = zero(SymmetricTensor{2,3}) - backstress1 = zero(SymmetricTensor{2,3}) - backstress2 = zero(SymmetricTensor{2,3}) - plastic_strain = zero(SymmetricTensor{2,3}) + stress = zero(Symm2) + strain = zero(Symm2) + backstress1 = zero(Symm2) + backstress2 = zero(Symm2) + plastic_strain = zero(Symm2) cumeq = 0.0 R = 0.0 state = AnotherState(stress, strain, backstress1, backstress2, plastic_strain, cumeq, R) for i in 1:N - dstress = randn(SymmetricTensor{2,3}) - dstrain = randn(SymmetricTensor{2,3}) - dbackstress1 = randn(SymmetricTensor{2,3}) - dbackstress2 = randn(SymmetricTensor{2,3}) - dplastic_strain = randn(SymmetricTensor{2,3}) + dstress = randn(Symm2) + dstrain = randn(Symm2) + dbackstress1 = randn(Symm2) + dbackstress2 = randn(Symm2) + dplastic_strain = randn(Symm2) dcumeq = norm(dplastic_strain) dR = randn() dstate = AnotherState(dstress, dstrain, dbackstress1, diff --git a/src/Materials.jl b/src/Materials.jl index d606d05..5abb00c 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -67,18 +67,8 @@ end export integrate_material!, update_material!, reset_material! -""" - isotropic_elasticity_tensor(lambda, mu) - -Compute the elasticity tensor (rank 4, symmetric) for an isotropic material -having the Lamé parameters `lambda` and `mu`. -""" -function isotropic_elasticity_tensor(lambda, mu) - delta(i,j) = i==j ? 1.0 : 0.0 - g(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k)) - jacobian = SymmetricTensor{4, 3, Float64}(g) - return jacobian -end +include("MaterialsUtils.jl") +export Symm2, Symm4, lame, delame, isotropic_elasticity_tensor, debang include("idealplastic.jl") export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlasticVariableState diff --git a/src/MaterialsUtils.jl b/src/MaterialsUtils.jl new file mode 100644 index 0000000..9479b3d --- /dev/null +++ b/src/MaterialsUtils.jl @@ -0,0 +1,92 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE + +"""Alias for symmetric tensor type of rank 2, dimension 3.""" +const Symm2{T} = SymmetricTensor{2,3,T} + +"""Alias for symmetric tensor type of rank 4, dimension 3.""" +const Symm4{T} = SymmetricTensor{4,3,T} + +""" + lame(E, nu) + +Convert the elastic parameters (E, nu) to the Lamé parameters (lambda, mu). Isotropic material. +""" +@inline function lame(E::Real, nu::Real) + lambda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) + mu = E / (2.0 * (1.0 + nu)) + return lambda, mu +end + +""" + delame(lambda, mu) + +Convert the Lamé parameters (lambda, mu) to the elastic parameters (E, nu). Isotropic material. +""" +@inline function delame(lambda::Real, mu::Real) + E = mu * (3.0 * lambda + 2.0 * mu) / (lambda + mu) + nu = lambda / (2.0 * (lambda + mu)) + return E, nu +end + +""" + isotropic_elasticity_tensor(lambda, mu) + +Compute the elasticity tensor (rank 4, symmetric) for an isotropic material +having the Lamé parameters `lambda` and `mu`. + +If you have (E, nu) instead, use `lame` to get (lambda, mu). +""" +function isotropic_elasticity_tensor(lambda, mu) + delta(i,j) = i==j ? 1.0 : 0.0 + C(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k)) + return Symm4{Float64}(C) +end + +""" + debang(f!, ex=nothing) + +Take away the bang; i.e. convert a mutating function into non-mutating form. + +`f!` must be a two-argument mutating function, which writes the result into its +first argument. The result of `debang` is then `f`, a single-argument +non-mutating function that allocates and returns the result. Schematically, +`f!(out, x)` becomes `f(x)`. + +When the type, size and shape of `out` is the same as those of `x`, it is enough +to supply just `f!`. When `f` is called, output will be allocated as `similar(x)`. + +When the type, size and/or shape of `out` are different from those of `x`, then +an example instance of the correct type with the correct size and shape for the +output must be supplied, as debang's `ex` argument. When `f` is called, output +will be allocated as `similar(ex)`. + +# Note + +While the type of F is known at compile time, the size and shape are typically +runtime properties, not encoded into the type. For example, arrays have the +number of dimensions as part of the type, but the length of each dimension +is only defined at run time, when an instance is created. This is why the `ex` +argument is needed. + +# Etymology + +By convention, mutating functions are marked with an exclamation mark, a.k.a. +bang. Debanging takes away the bang. +""" +function debang(f!, ex=nothing) + if ex === nothing + function f(x) + out = similar(x) + f!(out, x) + return out + end + else + function f(x) + out = similar(ex) + f!(out, x) + return out + end + end + return f +end diff --git a/src/biaxial_increment.jl b/src/biaxial_increment.jl index 3539162..4493091 100644 --- a/src/biaxial_increment.jl +++ b/src/biaxial_increment.jl @@ -9,7 +9,7 @@ stress0 = tovoigt(material.variables.stress) for i=1:max_iter material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(SymmetricTensor{2,3,Float64}, dstrain; offdiagscale=2.0) + material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) integrate_material!(material) stress = tovoigt(material.variables_new.stress) dstress = stress - stress0 diff --git a/src/chaboche.jl b/src/chaboche.jl index 78c6851..ee9febe 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -1,12 +1,6 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE -"""Alias for symmetric tensor type of rank 2, dimension 3.""" -const Symm2{T} = SymmetricTensor{2,3,T} - -"""Alias for symmetric tensor type of rank 4, dimension 3.""" -const Symm4{T} = SymmetricTensor{4,3,T} - @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) strain :: Symm2 = zero(Symm2{Float64}) @@ -45,28 +39,6 @@ end dparameters :: ChabocheParameterState = ChabocheParameterState() end -""" - lame(E, ν) - -Convert the elastic parameters (E, ν) to the Lamé parameters (μ, λ). Isotropic material. -""" -@inline function lame(E::Real, ν::Real) - μ = E / (2.0 * (1.0 + nu)) - λ = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) - return μ, λ -end - -""" - delame(E, ν) - -Convert the Lamé parameters (μ, λ) to the elastic parameters (E, ν). Isotropic material. -""" -@inline function delame(μ::Real, λ::Real) - E = μ * (3.0 * λ + 2.0 * μ) / (λ + μ) - ν = λ / (2.0 * (λ + μ)) - return E, ν -end - """ state_to_vector(σ::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real @@ -89,61 +61,13 @@ Unmarshal the problem state from a `Vector`. Adaptor for `nlsolve`. return σ, R, X1, X2 end -""" - debang(f!, ex=nothing) - -Take away the bang; i.e. convert a mutating function into non-mutating form. - -`f!` must be a two-argument mutating function, which writes the result into its -first argument. The result of `debang` is then `f`, a single-argument -non-mutating function that allocates and returns the result. Schematically: - -```julia - f!(out, x) -> f(x) -``` - -When the type, size and shape of `out` is the same as those of `x`, it is enough -to supply just `f!`. When `f` is called, output will be allocated as `similar(x)`. - -When the type, size and/or shape of `out` are different from those of `x`, then -an example instance of the correct type with the correct size and shape for the -output must be supplied, as debang's `ex` argument. When `f` is called, output -will be allocated as `similar(ex)`. - -(While the type of F is known at compile time, the size and shape are typically -runtime properties, not encoded into the type. For example, arrays have the -number of dimensions encoded into the type, but the length of each dimension -is defined at run time, when an instance is created.) - -# Etymology - -By convention, mutating functions are marked with an exclamation mark, a.k.a. -bang. Debanging takes away the bang. -""" -function debang(f!, ex=nothing) - if ex === nothing - function f(x) - out = similar(x) - f!(out, x) - return out - end - else - function f(x) - out = similar(ex) - f!(out, x) - return out - end - end - return f -end - function integrate_material!(material::Chaboche) p = material.parameters v = material.variables dd = material.ddrivers d = material.drivers @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p - mu, lambda = lame(E, nu) + lambda, mu = lame(E, nu) @unpack strain, time = d dstrain = dd.strain @@ -232,7 +156,7 @@ function create_nonlinear_system_of_equations(material::Chaboche) dd = material.ddrivers d = material.drivers @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b = p - mu, lambda = lame(E, nu) + lambda, mu = lame(E, nu) # Old problem state (i.e. the problem state at the time when this equation # system instance was created). diff --git a/src/idealplastic.jl b/src/idealplastic.jl index 50db7e5..8db4266 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -3,7 +3,7 @@ @with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) - strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + strain :: Symm2 = zero(Symm2{Float64}) end @with_kw struct IdealPlasticParameterState <: AbstractMaterialState @@ -13,10 +13,10 @@ end end @with_kw struct IdealPlasticVariableState <: AbstractMaterialState - stress :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) - plastic_strain :: SymmetricTensor{2,3} = zero(SymmetricTensor{2,3,Float64}) + stress :: Symm2 = zero(Symm2{Float64}) + plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) - jacobian :: SymmetricTensor{4,3} = zero(SymmetricTensor{4,3,Float64}) + jacobian :: Symm4 = zero(Symm4{Float64}) end @with_kw mutable struct IdealPlastic <: AbstractMaterial @@ -59,8 +59,8 @@ function integrate_material!(material::IdealPlastic) stress -= dcontract(jacobian, dp*n) delta(i,j) = i==j ? 1.0 : 0.0 - II = SymmetricTensor{4,3}((i,j,k,l) -> 0.5*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k))) - P = II - 1.0/3.0*SymmetricTensor{4,3}((i,j,k,l) -> delta(i,j)*delta(k,l)) + II = Symm4((i,j,k,l) -> 0.5*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k))) + P = II - 1.0/3.0*Symm4((i,j,k,l) -> delta(i,j)*delta(k,l)) EE = II + dp*dcontract(jacobian, 1.5*P/R0 - otimes(n,n)/R0) ED = dcontract(inv(EE),jacobian) jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED))/dcontract(dcontract(n, ED), n) diff --git a/src/stress_driven_uniaxial_increment.jl b/src/stress_driven_uniaxial_increment.jl index 8efa960..15f5833 100644 --- a/src/stress_driven_uniaxial_increment.jl +++ b/src/stress_driven_uniaxial_increment.jl @@ -7,7 +7,7 @@ function stress_driven_uniaxial_increment!(material, dstress11, stress0 = tovoigt(material.variables.stress) for i=1:max_iter material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(SymmetricTensor{2,3,Float64}, dstrain; offdiagscale=2.0) + material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) integrate_material!(material) stress = tovoigt(material.variables_new.stress) dstress = stress - stress0 diff --git a/src/uniaxial_increment.jl b/src/uniaxial_increment.jl index 4479385..95383f4 100644 --- a/src/uniaxial_increment.jl +++ b/src/uniaxial_increment.jl @@ -8,7 +8,7 @@ stress0 = tovoigt(material.variables.stress) for i=1:max_iter material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(SymmetricTensor{2,3,Float64}, dstrain; offdiagscale=2.0) + material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) integrate_material!(material) stress = tovoigt(material.variables_new.stress) dstress = stress - stress0 diff --git a/test/test_chaboche.jl b/test/test_chaboche.jl index 610a45a..c166cc3 100644 --- a/test/test_chaboche.jl +++ b/test/test_chaboche.jl @@ -37,7 +37,7 @@ chabmat = Chaboche(parameters = parameters) s33s = [chabmat.variables.stress[3,3]] for i=2:length(ts) dtime = ts[i]-ts[i-1] - dstrain = fromvoigt(SymmetricTensor{2,3,Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) + dstrain = fromvoigt(Symm2{Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) chabmat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) integrate_material!(chabmat) update_material!(chabmat) diff --git a/test/test_chaboche_shear.jl b/test/test_chaboche_shear.jl index d60a854..b9c4c94 100644 --- a/test/test_chaboche_shear.jl +++ b/test/test_chaboche_shear.jl @@ -60,7 +60,7 @@ for i=2:length(times) dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] # mat.dtime = dtime # mat.dstrain = dstrain - dstrain_ = fromvoigt(SymmetricTensor{2,3,Float64}, dstrain; offdiagscale=2.0) + dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) mat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain_) integrate_material!(mat) # mat.time += mat.dtime diff --git a/test/test_idealplastic.jl b/test/test_idealplastic.jl index 9fb32f5..bff2df6 100644 --- a/test/test_idealplastic.jl +++ b/test/test_idealplastic.jl @@ -17,14 +17,14 @@ parameters = IdealPlasticParameterState(youngs_modulus = 200.0e3, poissons_ratio = 0.3, yield_stress = 100.0) -dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64},1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) +dstrain_dtime = fromvoigt(Symm2{Float64},1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = IdealPlasticDriverState(time = 0.25, strain = 0.25*dstrain_dtime) mat = IdealPlastic(parameters=parameters, ddrivers=ddrivers) # mat.dtime = 0.25 # mat.dstrain .= 1.0e-3*[-0.3, -0.3, 1.0, 0.0, 0.0, 0.0]*mat.dtime integrate_material!(mat) update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(SymmetricTensor{2,3}, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0])) +@test isapprox(mat.variables.stress, fromvoigt(Symm2, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0])) # mat.time += mat.dtime # mat.strain .+= mat.dstrain @@ -32,31 +32,31 @@ update_material!(mat) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(SymmetricTensor{2,3}, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) +@test isapprox(mat.variables.stress, fromvoigt(Symm2, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) @test isapprox(mat.variables.cumeq, 0.0; atol=1.0e-12) # mat.time += mat.dtime # mat.strain .+= mat.dstrain # mat.stress .+= mat.dstress # mat.dstrain[:] .= 1.0e-3*[-0.5, -0.5, 1.0, 0.0, 0.0, 0.0]*mat.dtime -dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0) +dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = IdealPlasticDriverState(time = 0.25, strain = 0.25*dstrain_dtime) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(SymmetricTensor{2,3}, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) +@test isapprox(mat.variables.stress, fromvoigt(Symm2, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) @test isapprox(mat.variables.cumeq, 0.25*1.0e-3) # mat.time += mat.dtime # mat.strain .+= mat.dstrain # mat.stress .+= mat.dstress # mat.dstrain[:] .= -1.0e-3*[-0.3, -0.3, 1.0, 0.0, 0.0, 0.0]*mat.dtime -dstrain_dtime = fromvoigt(SymmetricTensor{2,3,Float64}, -1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) +dstrain_dtime = fromvoigt(Symm2{Float64}, -1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) ddrivers = IdealPlasticDriverState(time = 0.25, strain = 0.25*dstrain_dtime) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(SymmetricTensor{2,3}, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) +@test isapprox(mat.variables.stress, fromvoigt(Symm2, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) # mat.time += mat.dtime # mat.strain .+= mat.dstrain @@ -65,12 +65,12 @@ update_material!(mat) # m1 = 1.0e-3*[-0.3, -0.3, 1.0, 0.0, 0.0, 0.0] # m2 = 1.0e-3*[-0.5, -0.5, 1.0, 0.0, 0.0, 0.0] # mat.dstrain[:] .= -m1*0.75 - m2*0.25 -dstrain_dtime = (-0.75*fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) - -0.25*fromvoigt(SymmetricTensor{2,3,Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0)) +dstrain_dtime = (-0.75*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) + -0.25*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0)) ddrivers = IdealPlasticDriverState(time = 1.0, strain = dstrain_dtime) mat.ddrivers = ddrivers integrate_material!(mat) integrate_material!(mat) update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(SymmetricTensor{2,3}, [-100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) +@test isapprox(mat.variables.stress, fromvoigt(Symm2, [-100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) # @test isapprox(mat.properties.dplastic_multiplier, 0.25*1.0e-3) diff --git a/test/test_idealplastic_shear.jl b/test/test_idealplastic_shear.jl index 2702b43..abf8a29 100644 --- a/test/test_idealplastic_shear.jl +++ b/test/test_idealplastic_shear.jl @@ -35,7 +35,7 @@ for i=2:length(times) dtime = times[i]-times[i-1] dstrain31 = loads[i]-loads[i-1] dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] - dstrain_ = fromvoigt(SymmetricTensor{2,3,Float64}, dstrain; offdiagscale=2.0) + dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) ddrivers = IdealPlasticDriverState(time = dtime, strain = dstrain_) #mat.dtime = dtime #mat.dstrain = dstrain From 13013d477741f132ecd88b6881ea17676d010d71 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 2 Jun 2020 16:27:04 +0300 Subject: [PATCH 07/66] More cleanup --- src/MaterialsUtils.jl | 10 ++++++++++ src/chaboche.jl | 25 ++++++++++++++----------- src/idealplastic.jl | 43 +++++++++++++++++++++++++++---------------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/MaterialsUtils.jl b/src/MaterialsUtils.jl index 9479b3d..fd0e84b 100644 --- a/src/MaterialsUtils.jl +++ b/src/MaterialsUtils.jl @@ -90,3 +90,13 @@ function debang(f!, ex=nothing) end return f end + +# Various rank-4 unit tensors, for documentation. +# Let A be a rank-2 tensor, and I the rank-2 unit tensor. +# +# II = Symm4{Float64}((i,j,k,l) -> delta(i,k)*delta(j,l)) # II : A = A +# IT = Symm4{Float64}((i,j,k,l) -> delta(i,l)*delta(j,k)) # IT : A = transpose(A) +# IS = Symm4{Float64}((i,j,k,l) -> 0.5*(II + IT)) # symmetric +# IA = Symm4{Float64}((i,j,k,l) -> 0.5*(II - IT)) # screw-symmetric +# IV = 1.0/3.0 * Symm4{Float64}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric, IV = (1/3) I ⊗ I +# ID = IS - IV # deviatoric diff --git a/src/chaboche.jl b/src/chaboche.jl index ee9febe..0348c58 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -40,27 +40,32 @@ end end """ - state_to_vector(σ::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real + state_to_vector(sigma::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real -Marshal the problem state into a `Vector`. Adaptor for `nlsolve`. +Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. """ -@inline function state_to_vector(σ::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real - return [tovoigt(σ), R, tovoigt(X1), tovoigt(X2)] +@inline function state_to_vector(sigma::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real + return [tovoigt(sigma), R, tovoigt(X1), tovoigt(X2)] end """ state_from_vector(x::AbstractVector{Real}) -Unmarshal the problem state from a `Vector`. Adaptor for `nlsolve`. +Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. """ @inline function state_from_vector(x::AbstractVector{Real}) - σ = fromvoigt(Symm2{S}, @view x[1:6]) + sigma = fromvoigt(Symm2{S}, @view x[1:6]) R = x[7] X1 = fromvoigt(Symm2{S}, @view x[8:13]) X2 = fromvoigt(Symm2{S}, @view x[14:19]) - return σ, R, X1, X2 + return sigma, R, X1, X2 end +""" + integrate_material!(material::Chaboche) + +Chaboche material with two backstresses. Both kinematic and isotropic hardening. +""" function integrate_material!(material::Chaboche) p = material.parameters v = material.variables @@ -111,8 +116,6 @@ function integrate_material!(material::Chaboche) # the other factor we will have to supply manually. drdx = ForwardDiff.jacobian(debang(g!), x) # Array{19, 19} drde = zeros((length(x),6)) # Array{19, 6} - # We are only interested in dσ/dε, so the rest of drde can be left as zeros. - # TODO: where does the minus sign come from? drde[1:6, 1:6] = -tovoigt(jacobian) # (negative of the) elastic Jacobian. Follows from the defn. of g!. jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end @@ -145,8 +148,8 @@ residual: ``` Both `F` (output) and `x` (input) are length-19 vectors containing -[σ, R, X1, X2], in that order. The tensor quantities σ, X1, X2 are -encoded in Voigt format. +[sigma, R, X1, X2], in that order. The tensor quantities sigma, X1, +X2 are encoded in Voigt format. The function `g!` is intended to be handed over to `nlsolve`. """ diff --git a/src/idealplastic.jl b/src/idealplastic.jl index 8db4266..8a6c03b 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -28,6 +28,12 @@ end dparameters :: IdealPlasticParameterState = IdealPlasticParameterState() end +""" + integrate_material!(material::IdealPlastic) + +Ideal plastic material: no hardening. The elastic region remains centered on the +origin, and retains its original size. +""" function integrate_material!(material::IdealPlastic) p = material.parameters v = material.variables @@ -36,34 +42,39 @@ function integrate_material!(material::IdealPlastic) E = p.youngs_modulus nu = p.poissons_ratio - mu = E/(2.0*(1.0+nu)) - lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + lambda, mu = lame(E, nu) R0 = p.yield_stress - # @unpack strain, time = d + # @unpack strain, time = d # not needed for this material dstrain = dd.strain dtime = dd.time @unpack stress, plastic_strain, cumeq, jacobian = v - jacobian = isotropic_elasticity_tensor(lambda, mu) - stress += dcontract(jacobian, dstrain) + jacobian = isotropic_elasticity_tensor(lambda, mu) # dσ/dε, i.e. ∂σij/∂εkl + stress += dcontract(jacobian, dstrain) # add the elastic stress increment, get the elastic trial stress seff_dev = dev(stress) - stress_v = sqrt(1.5)*norm(seff_dev) - f = stress_v - R0 + f = sqrt(1.5)*norm(seff_dev) - R0 # von Mises yield function; f := J(seff_dev) - Y + + if f > 0.0 + dp = 1.0/(3.0*mu) * f + n = sqrt(1.5)*seff_dev/norm(seff_dev) # a (tensorial) unit direction, s.t. 2/3 * (n : n) = 1 - if f>0.0 - n = 1.5*seff_dev/stress_v - dp = 1.0/(3.0*mu)*(stress_v - R0) plastic_strain += dp*n - cumeq += dp + cumeq += dp # cumulative equivalent plastic strain (note dp ≥ 0) + # Ideal plastic material: the stress state cannot be outside the yield surface. + # Project it back to the yield surface. stress -= dcontract(jacobian, dp*n) + + # Compute ∂σij/∂εkl, accounting for the plastic contribution. delta(i,j) = i==j ? 1.0 : 0.0 - II = Symm4((i,j,k,l) -> 0.5*(delta(i,k)*delta(j,l)+delta(i,l)*delta(j,k))) - P = II - 1.0/3.0*Symm4((i,j,k,l) -> delta(i,j)*delta(k,l)) - EE = II + dp*dcontract(jacobian, 1.5*P/R0 - otimes(n,n)/R0) - ED = dcontract(inv(EE),jacobian) - jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED))/dcontract(dcontract(n, ED), n) + II = Symm4{Float64}((i,j,k,l) -> 0.5*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k))) # symmetric + V = 1.0/3.0 * Symm4{Float64}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric + P = II - V # deviatoric + EE = II + dp/R0 * dcontract(jacobian, 1.5*P - otimes(n,n)) # using the elastic jacobian + ED = dcontract(inv(EE), jacobian) + # J = ED - (ED : n) ⊗ (n : ED) / (n : ED : n) + jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED)) / dcontract(dcontract(n, ED), n) end variables_new = IdealPlasticVariableState(stress=stress, plastic_strain=plastic_strain, From 55bfe7116778445a477e8477022cf6c3413b581c Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 2 Jun 2020 16:28:40 +0300 Subject: [PATCH 08/66] Better name for the utilities module --- src/Materials.jl | 2 +- src/{MaterialsUtils.jl => utilities.jl} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{MaterialsUtils.jl => utilities.jl} (100%) diff --git a/src/Materials.jl b/src/Materials.jl index 5abb00c..bc3b489 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -67,7 +67,7 @@ end export integrate_material!, update_material!, reset_material! -include("MaterialsUtils.jl") +include("utilities.jl") export Symm2, Symm4, lame, delame, isotropic_elasticity_tensor, debang include("idealplastic.jl") diff --git a/src/MaterialsUtils.jl b/src/utilities.jl similarity index 100% rename from src/MaterialsUtils.jl rename to src/utilities.jl From ee1d63dba8c1a70f5b7c0647a78839baedc04d36 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 3 Jun 2020 17:35:59 +0300 Subject: [PATCH 09/66] More cleanup --- src/Materials.jl | 21 +++++++---- src/biaxial_increment.jl | 46 ++++++++++++++++++++----- src/stress_driven_uniaxial_increment.jl | 41 +++++++++++++++++++--- src/uniaxial_increment.jl | 40 ++++++++++++++++++--- 4 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/Materials.jl b/src/Materials.jl index bc3b489..343db46 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -27,11 +27,15 @@ export AbstractMaterial, AbstractMaterialState """ integrate_material!(material::M) where {M<:AbstractMaterial} -Integrate one timestep. The input `material` represents the problem state at the -end of the previous timestep. +Integrate one timestep. The input `material.variables` represents the old +problem state. Abstract method. Must be implemented for each material type. When integration is -done, the method must update the `material` argument to have the new state. +done, the method **must** write the new state into `material.variables_new`. + +**Do not** write into `material.variables`; actually committing the timestep +(i.e. accepting that one step of time evolution and applying it permanently) +is the job of `update_material!`. """ function integrate_material!(material::M) where {M<:AbstractMaterial} error("One needs to define how to integrate material $M!") @@ -40,8 +44,11 @@ end """ update_material!(material::M) where {M <: AbstractMaterial} -In `material`, add `ddrivers` into `drivers`, `dparameters` into `parameters`, -and replace `variables` by `variables_new`. Then `reset_material!`. +Commit the result of `integrate_material!`. + +In `material`, we add `ddrivers` into `drivers`, `dparameters` into +`parameters`, and replace `variables` by `variables_new`. Then we +automatically invoke `reset_material!`. """ function update_material!(material::M) where {M <: AbstractMaterial} material.drivers += material.ddrivers @@ -54,7 +61,9 @@ end """ reset_material!(material::M) where {M <: AbstractMaterial} -In `material`, zero out `ddrivers`, `dparameters` and `variables_new`. +In `material`, we zero out `ddrivers`, `dparameters` and `variables_new`. This +clears out the tentative state produced when a timestep has been computed, but +has not yet been committed. Used internally by `update_material!`. """ diff --git a/src/biaxial_increment.jl b/src/biaxial_increment.jl index 4493091..c9f3b2b 100644 --- a/src/biaxial_increment.jl +++ b/src/biaxial_increment.jl @@ -1,22 +1,52 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - function biaxial_increment!(material, dstrain11, dstrain12, - dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], # Voigt notation - max_iter=50, norm_acc=1e-9) +# TODO: This docstring is a wild guess, based on reading the code. Please check. Especially, what is the use case of this routine? +""" + biaxial_increment!(material, dstrain11, dstrain12, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the components 11 and 12 of the +*strain* increment are taken as prescribed. This routine computes the other +components of the strain increment such that the stress state predicted by +integrating the material for one timestep matches the stress state stored in +`materials.variables.stress`. The computation starts from the initial guess +`dstrain`. + +To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl +(`material.variables_new.jacobian`) provided by the material implementation. + +This process is iterated at most `max_iter` times, until the non-prescribed +components of `dstrain` converge to within `norm_acc` (in a vector norm +sense). + +If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is +thrown. + +Note the timestep is **not** committed; we call `integrate_material!`, but not +`update_material!`. Only `material.variables_new` is updated. +""" +function biaxial_increment!(material, dstrain11, dstrain12, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], + max_iter=50, norm_acc=1e-9) converged = false - stress0 = tovoigt(material.variables.stress) + stress0 = tovoigt(material.variables.stress) # observed for i=1:max_iter material.ddrivers.time = dt material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) integrate_material!(material) - stress = tovoigt(material.variables_new.stress) + stress = tovoigt(material.variables_new.stress) # predicted dstress = stress - stress0 D = tovoigt(material.variables_new.jacobian) - dstr = -D[2:end-1,2:end-1]\dstress[2:end-1] + dstr = -D[2:end-1,2:end-1] \ dstress[2:end-1] dstrain[2:end-1] .+= dstr - norm(dstr) < norm_acc && (converged = true; break) + if norm(dstr) < norm_acc + converged = true + break + end end converged || error("No convergence in strain increment") return nothing diff --git a/src/stress_driven_uniaxial_increment.jl b/src/stress_driven_uniaxial_increment.jl index 15f5833..4888fa4 100644 --- a/src/stress_driven_uniaxial_increment.jl +++ b/src/stress_driven_uniaxial_increment.jl @@ -1,8 +1,36 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE -function stress_driven_uniaxial_increment!(material, dstress11, - dt; dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], max_iter=50, norm_acc=1e-9) +# TODO: This docstring is a wild guess, based on reading the code. Please check. Especially, what is the use case of this routine? +""" + stress_driven_uniaxial_increment!(material, dstress11, dt; + dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the component 11 of the *stress* +increment are taken as prescribed. This routine computes a strain increment such +that the stress state predicted by integrating the material for one timestep +matches the one stored in `materials.variables.stress`. The computation starts +from the initial guess `dstrain`. + +To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl +(`material.variables_new.jacobian`) provided by the material implementation. + +This process is iterated at most `max_iter` times, until the non-prescribed +components of `dstrain` converge to within `norm_acc` (in a vector norm +sense). + +If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is +thrown. + +Note the timestep is **not** committed; we call `integrate_material!`, but not +`update_material!`. Only `material.variables_new` is updated. +""" +function stress_driven_uniaxial_increment!(material, dstress11, dt; + dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) converged = false stress0 = tovoigt(material.variables.stress) for i=1:max_iter @@ -11,12 +39,15 @@ function stress_driven_uniaxial_increment!(material, dstress11, integrate_material!(material) stress = tovoigt(material.variables_new.stress) dstress = stress - stress0 - r = dstress + r = dstress r[1] -= dstress11 D = tovoigt(material.variables_new.jacobian) - dstr = -D\r + dstr = -D \ r dstrain .+= dstr - norm(dstr) < norm_acc && (converged = true; break) + if norm(dstr) < norm_acc + converged = true + break + end end converged || error("No convergence in strain increment") return nothing diff --git a/src/uniaxial_increment.jl b/src/uniaxial_increment.jl index 95383f4..b9ea8c8 100644 --- a/src/uniaxial_increment.jl +++ b/src/uniaxial_increment.jl @@ -1,9 +1,36 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - function uniaxial_increment!(material, dstrain11, - dt; dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) +""" + uniaxial_increment!(material, dstrain11, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the component 11 of the *strain* +increment are taken as prescribed. This routine computes the other components of +the strain increment such that the stress state predicted by integrating the +material for one timestep matches the stress state stored in +`materials.variables.stress`. The computation starts from the initial guess +`dstrain`. + +To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl +(`material.variables_new.jacobian`) provided by the material implementation. + +This process is iterated at most `max_iter` times, until the non-prescribed +components of `dstrain` converge to within `norm_acc` (in a vector norm +sense). + +If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is +thrown. + +Note the timestep is **not** committed; we call `integrate_material!`, but not +`update_material!`. Only `material.variables_new` is updated. +""" +function uniaxial_increment!(material, dstrain11, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) converged = false stress0 = tovoigt(material.variables.stress) for i=1:max_iter @@ -13,9 +40,12 @@ stress = tovoigt(material.variables_new.stress) dstress = stress - stress0 D = tovoigt(material.variables_new.jacobian) - dstr = -D[2:end,2:end]\dstress[2:end] + dstr = -D[2:end,2:end] \ dstress[2:end] dstrain[2:end] .+= dstr - norm(dstr) < norm_acc && (converged = true; break) + if norm(dstr) < norm_acc + converged = true + break + end end converged || error("No convergence in strain increment") return nothing From 12feba4e20f06bf9975ebb056d6525ae6a8ec50d Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 3 Jun 2020 17:39:22 +0300 Subject: [PATCH 10/66] Reorganize *_increment!() functions --- src/Materials.jl | 10 +- src/biaxial_increment.jl | 53 -------- src/increments.jl | 153 ++++++++++++++++++++++++ src/stress_driven_uniaxial_increment.jl | 54 --------- src/uniaxial_increment.jl | 52 -------- 5 files changed, 155 insertions(+), 167 deletions(-) delete mode 100644 src/biaxial_increment.jl create mode 100644 src/increments.jl delete mode 100644 src/stress_driven_uniaxial_increment.jl delete mode 100644 src/uniaxial_increment.jl diff --git a/src/Materials.jl b/src/Materials.jl index 343db46..193001d 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -88,13 +88,7 @@ export Chaboche, ChabocheDriverState, ChabocheParameterState, ChabocheVariableSt # include("viscoplastic.jl") # export ViscoPlastic -include("uniaxial_increment.jl") -export uniaxial_increment! - -include("biaxial_increment.jl") -export biaxial_increment! - -include("stress_driven_uniaxial_increment.jl") -export stress_driven_uniaxial_increment! +include("increments.jl") +export uniaxial_increment!, biaxial_increment!, stress_driven_uniaxial_increment! end diff --git a/src/biaxial_increment.jl b/src/biaxial_increment.jl deleted file mode 100644 index c9f3b2b..0000000 --- a/src/biaxial_increment.jl +++ /dev/null @@ -1,53 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -# TODO: This docstring is a wild guess, based on reading the code. Please check. Especially, what is the use case of this routine? -""" - biaxial_increment!(material, dstrain11, dstrain12, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], - max_iter=50, norm_acc=1e-9) - -Find a compatible strain increment for `material`. - -The material state (`material.variables`) and the components 11 and 12 of the -*strain* increment are taken as prescribed. This routine computes the other -components of the strain increment such that the stress state predicted by -integrating the material for one timestep matches the stress state stored in -`materials.variables.stress`. The computation starts from the initial guess -`dstrain`. - -To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl -(`material.variables_new.jacobian`) provided by the material implementation. - -This process is iterated at most `max_iter` times, until the non-prescribed -components of `dstrain` converge to within `norm_acc` (in a vector norm -sense). - -If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is -thrown. - -Note the timestep is **not** committed; we call `integrate_material!`, but not -`update_material!`. Only `material.variables_new` is updated. -""" -function biaxial_increment!(material, dstrain11, dstrain12, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], - max_iter=50, norm_acc=1e-9) - converged = false - stress0 = tovoigt(material.variables.stress) # observed - for i=1:max_iter - material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - integrate_material!(material) - stress = tovoigt(material.variables_new.stress) # predicted - dstress = stress - stress0 - D = tovoigt(material.variables_new.jacobian) - dstr = -D[2:end-1,2:end-1] \ dstress[2:end-1] - dstrain[2:end-1] .+= dstr - if norm(dstr) < norm_acc - converged = true - break - end - end - converged || error("No convergence in strain increment") - return nothing -end diff --git a/src/increments.jl b/src/increments.jl new file mode 100644 index 0000000..a3485a2 --- /dev/null +++ b/src/increments.jl @@ -0,0 +1,153 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE + +""" + uniaxial_increment!(material, dstrain11, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the component 11 of the *strain* +increment are taken as prescribed. This routine computes the other components of +the strain increment such that the stress state predicted by integrating the +material for one timestep matches the stress state stored in +`materials.variables.stress`. The computation starts from the initial guess +`dstrain`. + +To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl +(`material.variables_new.jacobian`) provided by the material implementation. + +This process is iterated at most `max_iter` times, until the non-prescribed +components of `dstrain` converge to within `norm_acc` (in a vector norm +sense). + +If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is +thrown. + +Note the timestep is **not** committed; we call `integrate_material!`, but not +`update_material!`. Only `material.variables_new` is updated. +""" +function uniaxial_increment!(material, dstrain11, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + converged = false + stress0 = tovoigt(material.variables.stress) + for i=1:max_iter + material.ddrivers.time = dt + material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) + integrate_material!(material) + stress = tovoigt(material.variables_new.stress) + dstress = stress - stress0 + D = tovoigt(material.variables_new.jacobian) + dstr = -D[2:end,2:end] \ dstress[2:end] + dstrain[2:end] .+= dstr + if norm(dstr) < norm_acc + converged = true + break + end + end + converged || error("No convergence in strain increment") + return nothing +end + +""" + biaxial_increment!(material, dstrain11, dstrain12, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the components 11 and 12 of the +*strain* increment are taken as prescribed. This routine computes the other +components of the strain increment such that the stress state predicted by +integrating the material for one timestep matches the stress state stored in +`materials.variables.stress`. The computation starts from the initial guess +`dstrain`. + +To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl +(`material.variables_new.jacobian`) provided by the material implementation. + +This process is iterated at most `max_iter` times, until the non-prescribed +components of `dstrain` converge to within `norm_acc` (in a vector norm +sense). + +If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is +thrown. + +Note the timestep is **not** committed; we call `integrate_material!`, but not +`update_material!`. Only `material.variables_new` is updated. +""" +function biaxial_increment!(material, dstrain11, dstrain12, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], + max_iter=50, norm_acc=1e-9) + converged = false + stress0 = tovoigt(material.variables.stress) # observed + for i=1:max_iter + material.ddrivers.time = dt + material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) + integrate_material!(material) + stress = tovoigt(material.variables_new.stress) # predicted + dstress = stress - stress0 + D = tovoigt(material.variables_new.jacobian) + dstr = -D[2:end-1,2:end-1] \ dstress[2:end-1] + dstrain[2:end-1] .+= dstr + if norm(dstr) < norm_acc + converged = true + break + end + end + converged || error("No convergence in strain increment") + return nothing +end + +""" + stress_driven_uniaxial_increment!(material, dstress11, dt; + dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the component 11 of the *stress* +increment are taken as prescribed. This routine computes a strain increment such +that the stress state predicted by integrating the material for one timestep +matches the one stored in `materials.variables.stress`. The computation starts +from the initial guess `dstrain`. + +To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl +(`material.variables_new.jacobian`) provided by the material implementation. + +This process is iterated at most `max_iter` times, until the non-prescribed +components of `dstrain` converge to within `norm_acc` (in a vector norm +sense). + +If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is +thrown. + +Note the timestep is **not** committed; we call `integrate_material!`, but not +`update_material!`. Only `material.variables_new` is updated. +""" +function stress_driven_uniaxial_increment!(material, dstress11, dt; + dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + converged = false + stress0 = tovoigt(material.variables.stress) + for i=1:max_iter + material.ddrivers.time = dt + material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) + integrate_material!(material) + stress = tovoigt(material.variables_new.stress) + dstress = stress - stress0 + r = dstress + r[1] -= dstress11 + D = tovoigt(material.variables_new.jacobian) + dstr = -D \ r + dstrain .+= dstr + if norm(dstr) < norm_acc + converged = true + break + end + end + converged || error("No convergence in strain increment") + return nothing +end diff --git a/src/stress_driven_uniaxial_increment.jl b/src/stress_driven_uniaxial_increment.jl deleted file mode 100644 index 4888fa4..0000000 --- a/src/stress_driven_uniaxial_increment.jl +++ /dev/null @@ -1,54 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -# TODO: This docstring is a wild guess, based on reading the code. Please check. Especially, what is the use case of this routine? -""" - stress_driven_uniaxial_increment!(material, dstress11, dt; - dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) - -Find a compatible strain increment for `material`. - -The material state (`material.variables`) and the component 11 of the *stress* -increment are taken as prescribed. This routine computes a strain increment such -that the stress state predicted by integrating the material for one timestep -matches the one stored in `materials.variables.stress`. The computation starts -from the initial guess `dstrain`. - -To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl -(`material.variables_new.jacobian`) provided by the material implementation. - -This process is iterated at most `max_iter` times, until the non-prescribed -components of `dstrain` converge to within `norm_acc` (in a vector norm -sense). - -If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is -thrown. - -Note the timestep is **not** committed; we call `integrate_material!`, but not -`update_material!`. Only `material.variables_new` is updated. -""" -function stress_driven_uniaxial_increment!(material, dstress11, dt; - dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) - converged = false - stress0 = tovoigt(material.variables.stress) - for i=1:max_iter - material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - integrate_material!(material) - stress = tovoigt(material.variables_new.stress) - dstress = stress - stress0 - r = dstress - r[1] -= dstress11 - D = tovoigt(material.variables_new.jacobian) - dstr = -D \ r - dstrain .+= dstr - if norm(dstr) < norm_acc - converged = true - break - end - end - converged || error("No convergence in strain increment") - return nothing -end diff --git a/src/uniaxial_increment.jl b/src/uniaxial_increment.jl deleted file mode 100644 index b9ea8c8..0000000 --- a/src/uniaxial_increment.jl +++ /dev/null @@ -1,52 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -""" - uniaxial_increment!(material, dstrain11, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) - -Find a compatible strain increment for `material`. - -The material state (`material.variables`) and the component 11 of the *strain* -increment are taken as prescribed. This routine computes the other components of -the strain increment such that the stress state predicted by integrating the -material for one timestep matches the stress state stored in -`materials.variables.stress`. The computation starts from the initial guess -`dstrain`. - -To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl -(`material.variables_new.jacobian`) provided by the material implementation. - -This process is iterated at most `max_iter` times, until the non-prescribed -components of `dstrain` converge to within `norm_acc` (in a vector norm -sense). - -If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is -thrown. - -Note the timestep is **not** committed; we call `integrate_material!`, but not -`update_material!`. Only `material.variables_new` is updated. -""" -function uniaxial_increment!(material, dstrain11, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) - converged = false - stress0 = tovoigt(material.variables.stress) - for i=1:max_iter - material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - integrate_material!(material) - stress = tovoigt(material.variables_new.stress) - dstress = stress - stress0 - D = tovoigt(material.variables_new.jacobian) - dstr = -D[2:end,2:end] \ dstress[2:end] - dstrain[2:end] .+= dstr - if norm(dstr) < norm_acc - converged = true - break - end - end - converged || error("No convergence in strain increment") - return nothing -end From 1890c344b53ef1d111a682486e716118a4c9bc32 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 3 Jun 2020 18:00:00 +0300 Subject: [PATCH 11/66] Unify the strain increment optimizers --- src/increments.jl | 149 +++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 89 deletions(-) diff --git a/src/increments.jl b/src/increments.jl index a3485a2..e93bf29 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -1,48 +1,48 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE +# The skeleton of the optimizer is always the same, so we provide it as a +# higher-order function. The individual specific optimizers only need to +# define the "meat" of how to update `dstrain`. """ - uniaxial_increment!(material, dstrain11, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) + optimize_dstrain!(material, dt, max_iter, tol, dstrain, update_dstrain!) Find a compatible strain increment for `material`. -The material state (`material.variables`) and the component 11 of the *strain* -increment are taken as prescribed. This routine computes the other components of -the strain increment such that the stress state predicted by integrating the -material for one timestep matches the stress state stored in -`materials.variables.stress`. The computation starts from the initial guess -`dstrain`. +`dstrain` should initially be a relevant initial guess. At each iteration, it is +updated by the user-defined corrector `update_dstrain!`, whose call signature +must be: -To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl -(`material.variables_new.jacobian`) provided by the material implementation. + update_dstrain!(dstrain, dstress, D) -> convergence error measure -This process is iterated at most `max_iter` times, until the non-prescribed -components of `dstrain` converge to within `norm_acc` (in a vector norm -sense). +`dstress` is the difference between the stress state predicted by integrating +the material for one timestep, and the stress state stored in +`materials.variables.stress`. -If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is -thrown. +`D` is the jacobian ∂σij/∂εkl (`material.variables_new.jacobian`), provided by +the material implementation. + +The update is iterated at most `max_iter` times, until the error measure +returned by `update_dstrain!` falls below `tol`. + +If `max_iter` is reached and the error measure is still `tol` or greater, +`ErrorException` is thrown. Note the timestep is **not** committed; we call `integrate_material!`, but not `update_material!`. Only `material.variables_new` is updated. """ -function uniaxial_increment!(material, dstrain11, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) +function optimize_dstrain!(material, dt, max_iter, tol, dstrain, update_dstrain!) converged = false - stress0 = tovoigt(material.variables.stress) + stress0 = tovoigt(material.variables.stress) # observed for i=1:max_iter material.ddrivers.time = dt material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) integrate_material!(material) - stress = tovoigt(material.variables_new.stress) + stress = tovoigt(material.variables_new.stress) # predicted dstress = stress - stress0 D = tovoigt(material.variables_new.jacobian) - dstr = -D[2:end,2:end] \ dstress[2:end] - dstrain[2:end] .+= dstr - if norm(dstr) < norm_acc + e = update_dstrain!(dstrain, dstress, D) + if e < tol converged = true break end @@ -51,6 +51,29 @@ function uniaxial_increment!(material, dstrain11, dt; return nothing end +""" + uniaxial_increment!(material, dstrain11, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + +Find a compatible strain increment for `material`. + +The material state (`material.variables`) and the component 11 of the *strain* +increment are taken as prescribed. This routine computes the other components of +the strain increment. See `optimize_dstrain!`. +""" +function uniaxial_increment!(material, dstrain11, dt; + dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter=50, norm_acc=1e-9) + function update_dstrain!(dstrain, dstress, D) + dstr = -D[2:end,2:end] \ dstress[2:end] + dstrain[2:end] .+= dstr + return norm(dstr) + end + optimize_dstrain!(material, dt, max_iter, norm_acc, dstrain, update_dstrain!) + return nothing +end + """ biaxial_increment!(material, dstrain11, dstrain12, dt; dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], @@ -60,44 +83,17 @@ Find a compatible strain increment for `material`. The material state (`material.variables`) and the components 11 and 12 of the *strain* increment are taken as prescribed. This routine computes the other -components of the strain increment such that the stress state predicted by -integrating the material for one timestep matches the stress state stored in -`materials.variables.stress`. The computation starts from the initial guess -`dstrain`. - -To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl -(`material.variables_new.jacobian`) provided by the material implementation. - -This process is iterated at most `max_iter` times, until the non-prescribed -components of `dstrain` converge to within `norm_acc` (in a vector norm -sense). - -If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is -thrown. - -Note the timestep is **not** committed; we call `integrate_material!`, but not -`update_material!`. Only `material.variables_new` is updated. +components of the strain increment. See `optimize_dstrain!`. """ function biaxial_increment!(material, dstrain11, dstrain12, dt; dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], max_iter=50, norm_acc=1e-9) - converged = false - stress0 = tovoigt(material.variables.stress) # observed - for i=1:max_iter - material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - integrate_material!(material) - stress = tovoigt(material.variables_new.stress) # predicted - dstress = stress - stress0 - D = tovoigt(material.variables_new.jacobian) + function update_dstrain!(dstrain, dstress, D) dstr = -D[2:end-1,2:end-1] \ dstress[2:end-1] dstrain[2:end-1] .+= dstr - if norm(dstr) < norm_acc - converged = true - break - end + return norm(dstr) end - converged || error("No convergence in strain increment") + optimize_dstrain!(material, dt, max_iter, norm_acc, dstrain, update_dstrain!) return nothing end @@ -109,45 +105,20 @@ end Find a compatible strain increment for `material`. The material state (`material.variables`) and the component 11 of the *stress* -increment are taken as prescribed. This routine computes a strain increment such -that the stress state predicted by integrating the material for one timestep -matches the one stored in `materials.variables.stress`. The computation starts -from the initial guess `dstrain`. - -To converge quickly, the update takes advantage of the jacobian ∂σij/∂εkl -(`material.variables_new.jacobian`) provided by the material implementation. - -This process is iterated at most `max_iter` times, until the non-prescribed -components of `dstrain` converge to within `norm_acc` (in a vector norm -sense). - -If `max_iter` is reached and `dstrain` has not converged, `ErrorException` is -thrown. - -Note the timestep is **not** committed; we call `integrate_material!`, but not -`update_material!`. Only `material.variables_new` is updated. +increment are taken as prescribed. This routine computes the strain increment. +See `optimize_dstrain!`. """ function stress_driven_uniaxial_increment!(material, dstress11, dt; dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], max_iter=50, norm_acc=1e-9) - converged = false - stress0 = tovoigt(material.variables.stress) - for i=1:max_iter - material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - integrate_material!(material) - stress = tovoigt(material.variables_new.stress) - dstress = stress - stress0 - r = dstress - r[1] -= dstress11 - D = tovoigt(material.variables_new.jacobian) - dstr = -D \ r + function update_dstrain!(dstrain, dstress, D) + # Mutation here doesn't matter, since `dstress` is overwritten at the start of each iteration. + # Note the lexical closure property gives us access to `dstress11` in this scope. + dstress[1] -= dstress11 + dstr = -D \ dstress dstrain .+= dstr - if norm(dstr) < norm_acc - converged = true - break - end + return norm(dstr) end - converged || error("No convergence in strain increment") + optimize_dstrain!(material, dt, max_iter, norm_acc, dstrain, update_dstrain!) return nothing end From 6a7cc7e82df983a3b6b24d29ee0298539aeea24d Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 4 Jun 2020 13:44:40 +0300 Subject: [PATCH 12/66] More cleanup --- src/chaboche.jl | 8 ++-- src/increments.jl | 118 +++++++++++++++++++++++++++------------------- src/utilities.jl | 15 +++--- 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 0348c58..3529977 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -49,11 +49,11 @@ Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. end """ - state_from_vector(x::AbstractVector{Real}) + state_from_vector(x::AbstractVector{<:Real}) Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. """ -@inline function state_from_vector(x::AbstractVector{Real}) +@inline function state_from_vector(x::AbstractVector{S}) where S <: Real sigma = fromvoigt(Symm2{S}, @view x[1:6]) R = x[7] X1 = fromvoigt(Symm2{S}, @view x[8:13]) @@ -144,7 +144,7 @@ The equation system is represented as a mutating function `g!` that computes the residual: ```julia - g!(F::V, x::V) where V <: AbstractVector{Real} + g!(F::V, x::V) where V <: AbstractVector{<:Real} ``` Both `F` (output) and `x` (input) are length-19 vectors containing @@ -175,7 +175,7 @@ function create_nonlinear_system_of_equations(material::Chaboche) # Compute the residual. F is output, x is filled by NLsolve. # The solution is x = x* such that g(x*) = 0. - function g!(F::V, x::V) where V <: AbstractVector{Real} + function g!(F::V, x::V) where V <: AbstractVector{<:Real} stress_, R_, X1_, X2_ = state_from_vector(x) # tentative new values from nlsolve seff_dev = dev(stress_ - X1_ - X2_) diff --git a/src/increments.jl b/src/increments.jl index e93bf29..c7f4585 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -2,28 +2,38 @@ # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE # The skeleton of the optimizer is always the same, so we provide it as a -# higher-order function. The individual specific optimizers only need to -# define the "meat" of how to update `dstrain`. +# higher-order function. The individual specific optimizer functions +# (`update_dstrain!)` only need to define the "meat" of how to update `dstrain`. """ - optimize_dstrain!(material, dt, max_iter, tol, dstrain, update_dstrain!) + optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, + dt::Real, update_dstrain!::Function; + max_iter::Integer=50, tol::Real=1e-9) Find a compatible strain increment for `material`. -`dstrain` should initially be a relevant initial guess. At each iteration, it is -updated by the user-defined corrector `update_dstrain!`, whose call signature -must be: +The `dstrain` supplied to this routine is the initial guess for the +optimization. At each iteration, it must be updated by the user-defined +corrector `update_dstrain!`, whose call signature is expected to be: - update_dstrain!(dstrain, dstress, D) -> convergence error measure + update_dstrain!(dstrain::V, dstress::V, jacobian::V) + where V <: AbstractVector{<:Real} + -> err::Real -`dstress` is the difference between the stress state predicted by integrating -the material for one timestep, and the stress state stored in -`materials.variables.stress`. +`dstrain` is the current value of the strain increment, in Voigt format. +Conversion to tensor format uses `offdiagscale=2.0`. The function must update +the Voigt format `dstrain` in-place. -`D` is the jacobian ∂σij/∂εkl (`material.variables_new.jacobian`), provided by -the material implementation. +`dstress = stress - stress0`, where `stress` is the stress state predicted by +integrating the material for one timestep of length `dt`, using the current +value of `dstrain` as a driving strain increment, and `stress0` is the stress +state stored in `materials.variables.stress`. -The update is iterated at most `max_iter` times, until the error measure -returned by `update_dstrain!` falls below `tol`. +`jacobian` is ∂σij/∂εkl (`material.variables_new.jacobian`), provided by the +material implementation. + +The return value `err` must be an error measure (Real, >= 0). + +The update is iterated at most `max_iter` times, until `err` falls below `tol`. If `max_iter` is reached and the error measure is still `tol` or greater, `ErrorException` is thrown. @@ -31,7 +41,9 @@ If `max_iter` is reached and the error measure is still `tol` or greater, Note the timestep is **not** committed; we call `integrate_material!`, but not `update_material!`. Only `material.variables_new` is updated. """ -function optimize_dstrain!(material, dt, max_iter, tol, dstrain, update_dstrain!) +function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, + dt::Real, update_dstrain!::Function; + max_iter::Integer=50, tol::Real=1e-9) converged = false stress0 = tovoigt(material.variables.stress) # observed for i=1:max_iter @@ -40,8 +52,8 @@ function optimize_dstrain!(material, dt, max_iter, tol, dstrain, update_dstrain! integrate_material!(material) stress = tovoigt(material.variables_new.stress) # predicted dstress = stress - stress0 - D = tovoigt(material.variables_new.jacobian) - e = update_dstrain!(dstrain, dstress, D) + jacobian = tovoigt(material.variables_new.jacobian) + e = update_dstrain!(dstrain, dstress, jacobian) if e < tol converged = true break @@ -52,73 +64,81 @@ function optimize_dstrain!(material, dt, max_iter, tol, dstrain, update_dstrain! end """ - uniaxial_increment!(material, dstrain11, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) + uniaxial_increment!(material::AbstractMaterial, dstrain11::Real, dt::Real; + dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter::Integer=50, norm_acc::Real=1e-9) Find a compatible strain increment for `material`. The material state (`material.variables`) and the component 11 of the *strain* increment are taken as prescribed. This routine computes the other components of -the strain increment. See `optimize_dstrain!`. +the strain increment such that the predicted stress state matches the stored +one. + +See `optimize_dstrain!`. """ -function uniaxial_increment!(material, dstrain11, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) - function update_dstrain!(dstrain, dstress, D) - dstr = -D[2:end,2:end] \ dstress[2:end] +function uniaxial_increment!(material::AbstractMaterial, dstrain11::Real, dt::Real; + dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], + max_iter::Integer=50, norm_acc::Real=1e-9) + function update_dstrain!(dstrain::V, dstress::V, jacobian::V) where V <: AbstractVector{<:Real} + dstr = -jacobian[2:end,2:end] \ dstress[2:end] dstrain[2:end] .+= dstr return norm(dstr) end - optimize_dstrain!(material, dt, max_iter, norm_acc, dstrain, update_dstrain!) + optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end """ - biaxial_increment!(material, dstrain11, dstrain12, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], - max_iter=50, norm_acc=1e-9) + biaxial_increment!(material::AbstractMaterial, dstrain11::Real, dstrain12::Real, dt::Real; + dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], + max_iter::Integer=50, norm_acc::Real=1e-9) Find a compatible strain increment for `material`. The material state (`material.variables`) and the components 11 and 12 of the *strain* increment are taken as prescribed. This routine computes the other -components of the strain increment. See `optimize_dstrain!`. +components of the strain increment such that the predicted stress state matches +the stored one. + +See `optimize_dstrain!`. """ -function biaxial_increment!(material, dstrain11, dstrain12, dt; - dstrain=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], - max_iter=50, norm_acc=1e-9) - function update_dstrain!(dstrain, dstress, D) - dstr = -D[2:end-1,2:end-1] \ dstress[2:end-1] +function biaxial_increment!(material::AbstractMaterial, dstrain11::Real, dstrain12::Real, dt::Real; + dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], + max_iter::Integer=50, norm_acc::Real=1e-9) + function update_dstrain!(dstrain::V, dstress::V, jacobian::V) where V <: AbstractVector{<:Real} + dstr = -jacobian[2:end-1,2:end-1] \ dstress[2:end-1] dstrain[2:end-1] .+= dstr return norm(dstr) end - optimize_dstrain!(material, dt, max_iter, norm_acc, dstrain, update_dstrain!) + optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end """ - stress_driven_uniaxial_increment!(material, dstress11, dt; - dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) + stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11::Real, dt::Real; + dstrain::AbstractVector{<:Real}=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], + max_iter::Integer=50, norm_acc::Real=1e-9) Find a compatible strain increment for `material`. The material state (`material.variables`) and the component 11 of the *stress* -increment are taken as prescribed. This routine computes the strain increment. +increment are taken as prescribed. This routine computes a strain increment such +that the predicted stress state matches the stored one. + See `optimize_dstrain!`. """ -function stress_driven_uniaxial_increment!(material, dstress11, dt; - dstrain=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], - max_iter=50, norm_acc=1e-9) - function update_dstrain!(dstrain, dstress, D) - # Mutation here doesn't matter, since `dstress` is overwritten at the start of each iteration. - # Note the lexical closure property gives us access to `dstress11` in this scope. +function stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11::Real, dt::Real; + dstrain::AbstractVector{<:Real}=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], + max_iter::Integer=50, norm_acc::Real=1e-9) + function update_dstrain!(dstrain::V, dstress::V, jacobian::V) where V <: AbstractVector{<:Real} + # Mutation of `dstress` doesn't matter, since `dstress` is freshly generated at each iteration. + # The lexical closure property gives us access to `dstress11` in this scope. dstress[1] -= dstress11 - dstr = -D \ dstress + dstr = -jacobian \ dstress dstrain .+= dstr return norm(dstr) end - optimize_dstrain!(material, dt, max_iter, norm_acc, dstrain, update_dstrain!) + optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end diff --git a/src/utilities.jl b/src/utilities.jl index fd0e84b..1898123 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -37,10 +37,10 @@ having the Lamé parameters `lambda` and `mu`. If you have (E, nu) instead, use `lame` to get (lambda, mu). """ -function isotropic_elasticity_tensor(lambda, mu) +function isotropic_elasticity_tensor(lambda::Real, mu::Real) delta(i,j) = i==j ? 1.0 : 0.0 C(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k)) - return Symm4{Float64}(C) + return Symm4{typeof(lambda)}(C) end """ @@ -94,9 +94,10 @@ end # Various rank-4 unit tensors, for documentation. # Let A be a rank-2 tensor, and I the rank-2 unit tensor. # -# II = Symm4{Float64}((i,j,k,l) -> delta(i,k)*delta(j,l)) # II : A = A -# IT = Symm4{Float64}((i,j,k,l) -> delta(i,l)*delta(j,k)) # IT : A = transpose(A) -# IS = Symm4{Float64}((i,j,k,l) -> 0.5*(II + IT)) # symmetric -# IA = Symm4{Float64}((i,j,k,l) -> 0.5*(II - IT)) # screw-symmetric -# IV = 1.0/3.0 * Symm4{Float64}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric, IV = (1/3) I ⊗ I +# typ = Float64 +# II = Symm4{typ}((i,j,k,l) -> delta(i,k)*delta(j,l)) # II : A = A +# IT = Symm4{typ}((i,j,k,l) -> delta(i,l)*delta(j,k)) # IT : A = transpose(A) +# IS = Symm4{typ}((i,j,k,l) -> 0.5*(II + IT)) # symmetric +# IA = Symm4{typ}((i,j,k,l) -> 0.5*(II - IT)) # screw-symmetric +# IV = 1.0/3.0 * Symm4{typ}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric, IV = (1/3) I ⊗ I # ID = IS - IV # deviatoric From a93c38b906cd075fcec78c184993cf00e5f50844 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 4 Jun 2020 13:46:27 +0300 Subject: [PATCH 13/66] Basic cleanup --- src/viscoplastic.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/viscoplastic.jl b/src/viscoplastic.jl index 125f0a9..d1ddec1 100644 --- a/src/viscoplastic.jl +++ b/src/viscoplastic.jl @@ -1,15 +1,13 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE + +# Viscoplastic material. See: +# http://www.solid.iei.liu.se/Education/TMHL55/TMHL55_lp1_2010/lecture_notes/plasticity_flow_rule_isotropic_hardening.pdf +# http://mms.ensmp.fr/msi_paris/transparents/Georges_Cailletaud/2013-GC-plas3D.pdf + using LinearAlgebra -# using ForwardDiff using NLsolve -# http://www.solid.iei.liu.se/Education/TMHL55/TMHL55_lp1_2010/lecture_notes/plasticity_flow_rule_isotropic_hardening.pdf -# http://mms.ensmp.fr/msi_paris/transparents/Georges_Cailletaud/2013-GC-plas3D.pdf - -##################################### -# Viscoplastic material definitions # -##################################### mutable struct ViscoPlastic <: AbstractMaterial # Material parameters youngs_modulus :: Float64 From 9bbe43d116559e1e1220b580979f540e9b22208c Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 4 Jun 2020 17:07:14 +0300 Subject: [PATCH 14/66] Cleanup --- src/Materials.jl | 16 +++++----- src/chaboche.jl | 2 +- src/utilities.jl | 42 +++++++++++++++++++------ test/test_uniaxial_increment.jl | 2 +- test/test_viscoplastic.jl | 4 +-- test/test_viscoplastic/plot_material.jl | 8 ++--- 6 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/Materials.jl b/src/Materials.jl index 193001d..1a1aeb9 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -9,7 +9,7 @@ abstract type AbstractMaterial end abstract type AbstractMaterialState end """ - :+(state::T, dstate::T) where {T <: AbstractMaterialState} + :+(state::T, dstate::T) where T <: AbstractMaterialState Addition for material states. @@ -17,7 +17,7 @@ Given two material states `state` and `dstate` of type `T`, add each field of `dstate` into the corresponding field of `state`. Return the resulting material state. """ -@generated function Base.:+(state::T, dstate::T) where {T <: AbstractMaterialState} +@generated function Base.:+(state::T, dstate::T) where T <: AbstractMaterialState expr = [:(state.$p + dstate.$p) for p in fieldnames(T)] return :(T($(expr...))) end @@ -25,7 +25,7 @@ end export AbstractMaterial, AbstractMaterialState """ - integrate_material!(material::M) where {M<:AbstractMaterial} + integrate_material!(material::M) where M <: AbstractMaterial Integrate one timestep. The input `material.variables` represents the old problem state. @@ -37,12 +37,12 @@ done, the method **must** write the new state into `material.variables_new`. (i.e. accepting that one step of time evolution and applying it permanently) is the job of `update_material!`. """ -function integrate_material!(material::M) where {M<:AbstractMaterial} +function integrate_material!(material::M) where M <: AbstractMaterial error("One needs to define how to integrate material $M!") end """ - update_material!(material::M) where {M <: AbstractMaterial} + update_material!(material::M) where M <: AbstractMaterial Commit the result of `integrate_material!`. @@ -50,7 +50,7 @@ In `material`, we add `ddrivers` into `drivers`, `dparameters` into `parameters`, and replace `variables` by `variables_new`. Then we automatically invoke `reset_material!`. """ -function update_material!(material::M) where {M <: AbstractMaterial} +function update_material!(material::M) where M <: AbstractMaterial material.drivers += material.ddrivers material.parameters += material.dparameters material.variables = material.variables_new @@ -59,7 +59,7 @@ function update_material!(material::M) where {M <: AbstractMaterial} end """ - reset_material!(material::M) where {M <: AbstractMaterial} + reset_material!(material::M) where M <: AbstractMaterial In `material`, we zero out `ddrivers`, `dparameters` and `variables_new`. This clears out the tentative state produced when a timestep has been computed, but @@ -67,7 +67,7 @@ has not yet been committed. Used internally by `update_material!`. """ -function reset_material!(material::M) where {M <: AbstractMaterial} +function reset_material!(material::M) where M <: AbstractMaterial material.ddrivers = typeof(material.ddrivers)() material.dparameters = typeof(material.dparameters)() material.variables_new = typeof(material.variables_new)() diff --git a/src/chaboche.jl b/src/chaboche.jl index 3529977..ceecffe 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -90,7 +90,7 @@ function integrate_material!(material::Chaboche) if f > 0.0 g! = create_nonlinear_system_of_equations(material) x0 = state_to_vector(stress, R, X1, X2) - res = nlsolve(g!, x0; autodiff = :forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl + res = nlsolve(g!, x0; autodiff=:forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl converged(res) || error("Nonlinear system of equations did not converge!") x = res.zero stress, R, X1, X2 = state_from_vector(x) diff --git a/src/utilities.jl b/src/utilities.jl index 1898123..ecf6474 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -7,6 +7,17 @@ const Symm2{T} = SymmetricTensor{2,3,T} """Alias for symmetric tensor type of rank 4, dimension 3.""" const Symm4{T} = SymmetricTensor{4,3,T} +# Various rank-4 unit tensors, for documentation. +# Let A be a rank-2 tensor, and I the rank-2 unit tensor. +# +# typ = Float64 +# II = Symm4{typ}((i,j,k,l) -> delta(i,k)*delta(j,l)) # II : A = A +# IT = Symm4{typ}((i,j,k,l) -> delta(i,l)*delta(j,k)) # IT : A = transpose(A) +# IS = Symm4{typ}((i,j,k,l) -> 0.5*(II + IT)) # symmetric +# IA = Symm4{typ}((i,j,k,l) -> 0.5*(II - IT)) # screw-symmetric +# IV = 1.0/3.0 * Symm4{typ}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric, IV = (1/3) I ⊗ I +# ID = IS - IV # deviatoric + """ lame(E, nu) @@ -91,13 +102,24 @@ function debang(f!, ex=nothing) return f end -# Various rank-4 unit tensors, for documentation. -# Let A be a rank-2 tensor, and I the rank-2 unit tensor. -# -# typ = Float64 -# II = Symm4{typ}((i,j,k,l) -> delta(i,k)*delta(j,l)) # II : A = A -# IT = Symm4{typ}((i,j,k,l) -> delta(i,l)*delta(j,k)) # IT : A = transpose(A) -# IS = Symm4{typ}((i,j,k,l) -> 0.5*(II + IT)) # symmetric -# IA = Symm4{typ}((i,j,k,l) -> 0.5*(II - IT)) # screw-symmetric -# IV = 1.0/3.0 * Symm4{typ}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric, IV = (1/3) I ⊗ I -# ID = IS - IV # deviatoric +# This comes from viscoplastic.jl, and is currently unused. +# The original error message suggested this was planned to be used for "radial return". +"""A simple Newton solver for x* such that f(x*) = 0.""" +function find_root(f::Function, x::AbstractVector{<:Real}, + dfdx::Union{Function, Nothing}=nothing; + max_iter::Integer=50, norm_acc::Real=1e-9) + if dfdx === nothing + dfdx = (x) -> ForwardDiff.jacobian(f, x) + end + converged = false + for i=1:max_iter + dx = -dfdx(x) \ f(x) + x += dx + if norm(dx) < norm_acc + converged = true + break + end + end + converged || error("No convergence!") + return x +end diff --git a/test/test_uniaxial_increment.jl b/test/test_uniaxial_increment.jl index 0886e4c..42aaef6 100644 --- a/test/test_uniaxial_increment.jl +++ b/test/test_uniaxial_increment.jl @@ -2,7 +2,7 @@ # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE using Test, Tensors dtime = 0.25 -# mat = Material(IdealPlastic, tuple()) +# mat = Material(IdealPlastic, ()) # mat.properties.youngs_modulus = 200.0e3 # mat.properties.poissons_ratio = 0.3 # mat.properties.yield_stress = 100.0 diff --git a/test/test_viscoplastic.jl b/test/test_viscoplastic.jl index f2822fb..29bdf11 100644 --- a/test/test_viscoplastic.jl +++ b/test/test_viscoplastic.jl @@ -13,7 +13,7 @@ using Materials, Test, ForwardDiff n = 1.0 K = 100.0 - mat = Material(ViscoPlastic, (:norton, [K, n])) + mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) mat.properties.youngs_modulus = 200.0e3 mat.properties.poissons_ratio = 0.3 mat.properties.yield_stress = 100.0 @@ -57,5 +57,5 @@ using Materials, Test, ForwardDiff end @testset "Invalid potential is not defined" begin - @test_throws ErrorException Material(ViscoPlastic, (:notNorton, [0.0, 0.0])) + @test_throws ErrorException Material(ViscoPlastic, (potential=:notNorton, params=[0.0, 0.0])) end diff --git a/test/test_viscoplastic/plot_material.jl b/test/test_viscoplastic/plot_material.jl index 1be750f..6f9cb5a 100644 --- a/test/test_viscoplastic/plot_material.jl +++ b/test/test_viscoplastic/plot_material.jl @@ -12,7 +12,7 @@ K = 180.0e3 # Simulation 1 -mat = Material(ViscoPlastic, (:norton, [K, n])) +mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) mat.properties.youngs_modulus = E mat.properties.poissons_ratio = poissons_ratio mat.properties.yield_stress = stress_y @@ -47,7 +47,7 @@ s11s = [s[1] for s in sim.stresses] e11s = [e[1] for e in strains] # Simulation 2 -mat = Material(ViscoPlastic, (:norton, [K, n])) +mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) mat.properties.youngs_modulus = E mat.properties.poissons_ratio = poissons_ratio mat.properties.yield_stress = stress_y @@ -74,7 +74,7 @@ s11s2 = [s[1] for s in sim.stresses] e11s2 = [e[1] for e in strains] # Simulation 3 -mat = Material(ViscoPlastic, (:norton, [K, n])) +mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) mat.properties.youngs_modulus = E mat.properties.poissons_ratio = poissons_ratio mat.properties.yield_stress = stress_y @@ -104,4 +104,4 @@ plot(e11s, s11s, label="de/dt=0.001346") plot(e11s2, s11s2, label="de/dt=0.00081") plot(e11s3, s11s3, label="de/dt=0.000008") grid() -show() \ No newline at end of file +show() From 0d1b70f5f621ac12d39279aba4f4401e9fee8555 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 4 Jun 2020 17:07:21 +0300 Subject: [PATCH 15/66] Start revising viscoplastic.jl. This is going to require a bit more work. --- src/viscoplastic.jl | 272 +++++++++++++++++++++++--------------------- 1 file changed, 145 insertions(+), 127 deletions(-) diff --git a/src/viscoplastic.jl b/src/viscoplastic.jl index d1ddec1..bf3d9cd 100644 --- a/src/viscoplastic.jl +++ b/src/viscoplastic.jl @@ -5,184 +5,202 @@ # http://www.solid.iei.liu.se/Education/TMHL55/TMHL55_lp1_2010/lecture_notes/plasticity_flow_rule_isotropic_hardening.pdf # http://mms.ensmp.fr/msi_paris/transparents/Georges_Cailletaud/2013-GC-plas3D.pdf -using LinearAlgebra +using LinearAlgebra # in Julia 1.0+, the I matrix lives here. using NLsolve -mutable struct ViscoPlastic <: AbstractMaterial - # Material parameters - youngs_modulus :: Float64 - poissons_ratio :: Float64 - yield_stress :: Float64 - potential :: Symbol - params :: Array{Float64, 1} +POTENTIAL_FUNCTIONS = [:norton] +# TODO: To conform with the API of the other models, viscoplastic still needs: +# - drivers, ddrivers, variables, variables_new, parameters, dparameters +# - ViscoPlasticDriverState: maybe time, strain? +# - ViscoPlasticParameterState: youngs_modulus, poissons_ratio, yield_stress, potential, params +# - ViscoPlasticVariableState: stress, plastic_strain, cumeq, jacobian +@with_kw mutable struct ViscoPlastic <: AbstractMaterial + # Material parameters + youngs_modulus::Float64 = zero(Float64) + poissons_ratio::Float64 = zero(Float64) + yield_stress::Float64 = zero(Float64) + potential::Symbol = :norton + # Parameters for potential. If potential=:norton, this is [K, n]. + params::Vector{Float64} = [180.0e3, 0.92] # Internal state variables - plastic_strain :: Vector{Float64} - dplastic_strain :: Vector{Float64} - plastic_multiplier :: Float64 - dplastic_multiplier :: Float64 + plastic_strain::Vector{Float64} = zeros(6) # TODO: never used? + dplastic_strain::Vector{Float64} = zeros(6) + plastic_multiplier::Float64 = zero(Float64) # TODO: never used, remove? + dplastic_multiplier::Float64 = zero(Float64) # TODO: never used, remove? + # https://mauro3.github.io/Parameters.jl/dev/manual/ + @assert potential in POTENTIAL_FUNCTIONS +end +# TODO: Rewrite this module to use regular tensor notation. +# Could then just use the functions from the Tensors package. +"""Double contraction of rank-2 tensors stored in an array format.""" +function dcontract(x::A, y::A) where A <: AbstractArray{<:Number, 2} + return sum(x .* y) end -POTENTIAL_FUNCTIONS = [:norton] +"""Deviatoric part of rank-2 tensor. -function ViscoPlastic(potential::Symbol, params :: Array{Float64,1}) - if !any(POTENTIAL_FUNCTIONS .== potential) - error("Potential $potential not found!") - end - youngs_modulus = 0.0 - poissons_ratio = 0.0 - yield_stress = 0.0 - plastic_strain = zeros(6) - dplastic_strain = zeros(6) - plastic_multiplier = 0.0 - dplastic_multiplier = 0.0 - dt = 0.0 - return ViscoPlastic(youngs_modulus, poissons_ratio, yield_stress, potential, params, - plastic_strain, dplastic_strain, plastic_multiplier, - dplastic_multiplier) -end +Input in Voigt format. Output as a 3×3 array`. -function ViscoPlastic() - youngs_modulus = 0.0 - poissons_ratio = 0.0 - yield_stress = 0.0 - plastic_strain = zeros(6) - dplastic_strain = zeros(6) - plastic_multiplier = 0.0 - dplastic_multiplier = 0.0 - dt = 0.0 - potential = :norton - params = [180.0e3, 0.92] - return ViscoPlastic(youngs_modulus, poissons_ratio, yield_stress, potential, params, - plastic_strain, dplastic_strain, plastic_multiplier, - dplastic_multiplier) -end +Order of components is: -""" Double contraction + t = [v[1] v[4] v[6]; + v[4] v[2] v[5]; + v[6] v[5] v[3]] """ -function double_contraction(x,y) - return sum(x.*y) +function dev(v::AbstractVector{<:Number}) + t = [v[1] v[4] v[6]; + v[4] v[2] v[5]; + v[6] v[5] v[3]] + # TODO: Unify storage order with the Julia standard: + # fromvoigt(SymmetricTensor{2,3}, Array(1:6)) + # + # 1 6 5 + # 6 2 4 + # 5 4 3 + # + # Changing this affects how to read sol_mat in dNortondStress. + return t - 1/3 * tr(t) * I end -""" Deviatoric stress tensor -""" -function deviatoric_stress(stress) - stress_tensor = [ stress[1] stress[4] stress[6]; - stress[4] stress[2] stress[5]; - stress[6] stress[5] stress[3]] - stress_dev = stress_tensor - 1/3 * tr(stress_tensor) * Matrix(1.0I, 3, 3) - return stress_dev +"""Equivalent stress.""" +function equivalent_stress(stress::AbstractVector{<:Number}) + s = dev(stress) + J_2 = 1/2 * dcontract(s,s) + return sqrt(3 * J_2) end -function J_2_stress(stress) - s = deviatoric_stress(stress) - return 1/2 * double_contraction(s,s) -end +"""Norton rule. -""" Equivalent stress -""" -function equivalent_stress(stress) - J_2 = J_2_stress(stress) - return sqrt(3 * J_2) -end +`params = [K, n]`. -""" Von mises yield function -""" -function von_mises_yield(stress, stress_y) - return equivalent_stress(stress) - stress_y -end +`f` is the yield function. -""" Norton rule +`stress` is passed through to `f` as its only argument, +so its storage format must be whatever `f` expects. """ -function norton_plastic_potential(stress, params, f) +function norton_plastic_potential(stress, params::AbstractVector{<:Number}, f::Function) K = params[1] n = params[2] - J = f(stress) - return K/(n+1) * (J / K) ^ (n + 1) + return K/(n+1) * (f(stress) / K)^(n + 1) end -function bingham_plastic_potential(stress, params, f) - eta = params[1] - return 0.5 * (f(stress) / eta) ^2 -end +"""Bingham rule. -""" Analytically derivated norton rule +`params = [eta]`. + +`f` and `stress` like in `norton_plastic_potential`. """ -function dNortondStress(stress, params, f) - K = params[1] - n = params[2] - f_ = f(stress) - stress_v = equivalent_stress(stress) - stress_dev_vec = deviatoric_stress(stress) - sol_mat = (f_ / K) ^ n * 3 / 2 * stress_dev_vec / stress_v - return [sol_mat[1,1], sol_mat[2,2], sol_mat[3,3], - 2*sol_mat[1,2], 2*sol_mat[2,3], 2*sol_mat[1,3]] +function bingham_plastic_potential(stress, params::AbstractVector{<:Number}, f::Function) + eta = params[1] + return 0.5 * (f(stress) / eta)^2 end -function find_root(f, df, x; max_iter=50, norm_acc=1e-9) - converged = false - for i=1:max_iter - dx = -df(x) \ f(x) - x += dx - norm(dx) < norm_acc && (converged = true; break) - end - converged || error("No convergence in radial return!") - return x +"""Jacobian of the Norton rule.""" +function dNortondStress(stress::AbstractVector{<:Number}, params::AbstractVector{<:Number}, f::Function) + # using SymPy + # @vars K n s + # @symfuns f + # norton = K / (n + 1) * (f(s) / K)^(n + 1) + # simplify(diff(norton, s)) + # --> (f(s) / K)^n df/ds + # + # ...so this only works if `f` is the von Mises yield function. + # + # K = params[1] + # n = params[2] + # stress_v = equivalent_stress(stress) + # stress_dev = dev(stress) + # sol_mat = (f(stress) / K)^n * 3.0/2.0 * stress_dev / stress_v + # return [sol_mat[1,1], sol_mat[2,2], sol_mat[3,3], + # 2*sol_mat[1,2], 2*sol_mat[2,3], 2*sol_mat[1,3]] + + # Instead, let's use AD: + pot(stress) = norton_plastic_potential(stress, params, f) + return ForwardDiff.jacobian(pot, stress) end -function integrate_material!(material::Material{ViscoPlastic}) +function integrate_material!(material::ViscoPlastic) mat = material.properties E = mat.youngs_modulus nu = mat.poissons_ratio + R0 = mat.yield_stress potential = mat.potential params = mat.params - mu = E/(2.0*(1.0+nu)) - lambda = E*nu/((1.0+nu)*(1.0-2.0*nu)) + lambda, mu = lame(E, nu) + # TODO: these fields don't exist! stress = material.stress strain = material.strain dstress = material.dstress dstrain = material.dstrain dt = material.dtime - D = material.jacobian + # D = material.jacobian # TODO: this field doesn't exist! dedt = dstrain ./ dt - fill!(D, 0.0) - D[1,1] = D[2,2] = D[3,3] = 2.0*mu + lambda - D[4,4] = D[5,5] = D[6,6] = mu - D[1,2] = D[2,1] = D[2,3] = D[3,2] = D[1,3] = D[3,1] = lambda + # This looks like a rank-4 tensor in a Voigt matrix format...? + # fill!(D, 0.0) + # D[1,1] = D[2,2] = D[3,3] = 2.0*mu + lambda + # D[4,4] = D[5,5] = D[6,6] = mu + # D[1,2] = D[2,1] = D[2,3] = D[3,2] = D[1,3] = D[3,1] = lambda + # + # Let's find out. In the REPL: + # S = SymmetricTensor{2, 3}(1:6) + # tovoigt(S) + # So the index mapping is: + # Voigt rank-2 + # 1 11 + # 2 22 + # 3 33 + # 4 23 + # 5 13 + # 6 12 + # So this is just the isotropic elasticity tensor C: + # Cijkl = λ δij δkl + μ (δik δjl + δil δjk) + # with the strain stored in a Voigt format: + # [εxx, εyy, εzz, εyz, εxz, εxy] (ordering of shear components doesn't matter here) + # + # So let's just: + D = tovoigt(isotropic_elasticity_tensor(lambda, mu)) + + """von Mises yield function.""" + function f(stress::AbstractVector{<:Number}) # stress in Voigt format + return equivalent_stress(stress) - R0 + end dstress[:] .= D * dedt .* dt - stress_tr = stress + dstress - f = x -> von_mises_yield(x, mat.yield_stress) - yield = f(stress_tr) + stress_elastic_trial = stress + dstress - if yield <= 0 + if f(stress_elastic_trial) <= 0 # not in plastic region fill!(mat.dplastic_strain, 0.0) return nothing + end + + if potential == :norton + pot(stress) -> norton_plastic_potential(stress, params, f) else - if potential == :norton - x0 = vec([dstress; 0.0]) - - function g!(F, x) - dsigma = x[1:6] - F[1:6] = dsigma - D * (dedt - dNortondStress(stress + dsigma, params, f)) .* dt - F[end] = von_mises_yield(stress + dsigma, mat.yield_stress) - end - - res = nlsolve(g!, x0) - dsigma = res.zero[1:6] - dstrain_pl = dNortondStress(stress + dsigma, params, f) - - end - mat.dplastic_strain = dstrain_pl - dstress[:] .= D*(dedt - dstrain_pl) .* dt - return nothing + @assert false # cannot happen (unless someone mutates the struct contents) end - return nothing + dpotdstress(stress) = ForwardDiff.jacobian(pot, stress) + # The nonlinear equation system, (stuff) = 0 + function g!(F, x) + dsigma = x[1:6] + F[1:6] = dsigma - D * (dedt - dpotdstress(stress + dsigma)) .* dt + F[end] = f(stress + dsigma) + end + + x0 = vec([dstress; 0.0]) + res = nlsolve(g!, x0, autodiff=:forward) + converged(res) || error("Nonlinear system of equations did not converge!") + dsigma = res.zero[1:6] + + dstrain_plastic = dpotdstress(stress + dsigma) + mat.dplastic_strain = dstrain_plastic + dstress[:] .= D * (dedt - dstrain_plastic) .* dt + return nothing end From 5653220025f6abaedf3ba6124aab38aa5964e4da Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 5 Jun 2020 17:47:06 +0300 Subject: [PATCH 16/66] No space around :: operator --- src/chaboche.jl | 53 +++++++++++++++++++++++---------------------- src/idealplastic.jl | 30 ++++++++++++------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index ceecffe..49fb1b3 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -2,41 +2,41 @@ # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState - time :: Float64 = zero(Float64) - strain :: Symm2 = zero(Symm2{Float64}) + time::Float64 = zero(Float64) + strain::Symm2 = zero(Symm2{Float64}) end @with_kw struct ChabocheParameterState <: AbstractMaterialState - E :: Float64 = 0.0 - nu :: Float64 = 0.0 - R0 :: Float64 = 0.0 - Kn :: Float64 = 0.0 - nn :: Float64 = 0.0 - C1 :: Float64 = 0.0 - D1 :: Float64 = 0.0 - C2 :: Float64 = 0.0 - D2 :: Float64 = 0.0 - Q :: Float64 = 0.0 - b :: Float64 = 0.0 + E::Float64 = 0.0 + nu::Float64 = 0.0 + R0::Float64 = 0.0 + Kn::Float64 = 0.0 + nn::Float64 = 0.0 + C1::Float64 = 0.0 + D1::Float64 = 0.0 + C2::Float64 = 0.0 + D2::Float64 = 0.0 + Q::Float64 = 0.0 + b::Float64 = 0.0 end @with_kw struct ChabocheVariableState <: AbstractMaterialState - stress :: Symm2 = zero(Symm2{Float64}) - X1 :: Symm2 = zero(Symm2{Float64}) - X2 :: Symm2 = zero(Symm2{Float64}) - plastic_strain :: Symm2 = zero(Symm2{Float64}) - cumeq :: Float64 = zero(Float64) - R :: Float64 = zero(Float64) - jacobian :: Symm4 = zero(Symm4{Float64}) + stress::Symm2 = zero(Symm2{Float64}) + X1::Symm2 = zero(Symm2{Float64}) + X2::Symm2 = zero(Symm2{Float64}) + plastic_strain::Symm2 = zero(Symm2{Float64}) + cumeq::Float64 = zero(Float64) + R::Float64 = zero(Float64) + jacobian::Symm4 = zero(Symm4{Float64}) end @with_kw mutable struct Chaboche <: AbstractMaterial - drivers :: ChabocheDriverState = ChabocheDriverState() - ddrivers :: ChabocheDriverState = ChabocheDriverState() - variables :: ChabocheVariableState = ChabocheVariableState() - variables_new :: ChabocheVariableState = ChabocheVariableState() - parameters :: ChabocheParameterState = ChabocheParameterState() - dparameters :: ChabocheParameterState = ChabocheParameterState() + drivers::ChabocheDriverState = ChabocheDriverState() + ddrivers::ChabocheDriverState = ChabocheDriverState() + variables::ChabocheVariableState = ChabocheVariableState() + variables_new::ChabocheVariableState = ChabocheVariableState() + parameters::ChabocheParameterState = ChabocheParameterState() + dparameters::ChabocheParameterState = ChabocheParameterState() end """ @@ -127,6 +127,7 @@ function integrate_material!(material::Chaboche) cumeq = cumeq, jacobian = jacobian) material.variables_new = variables_new + return nothing end """ diff --git a/src/idealplastic.jl b/src/idealplastic.jl index 8a6c03b..df6dae2 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -2,30 +2,30 @@ # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE @with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState - time :: Float64 = zero(Float64) - strain :: Symm2 = zero(Symm2{Float64}) + time::Float64 = zero(Float64) + strain::Symm2 = zero(Symm2{Float64}) end @with_kw struct IdealPlasticParameterState <: AbstractMaterialState - youngs_modulus :: Float64 = zero(Float64) - poissons_ratio :: Float64 = zero(Float64) - yield_stress :: Float64 = zero(Float64) + youngs_modulus::Float64 = zero(Float64) + poissons_ratio::Float64 = zero(Float64) + yield_stress::Float64 = zero(Float64) end @with_kw struct IdealPlasticVariableState <: AbstractMaterialState - stress :: Symm2 = zero(Symm2{Float64}) - plastic_strain :: Symm2 = zero(Symm2{Float64}) - cumeq :: Float64 = zero(Float64) - jacobian :: Symm4 = zero(Symm4{Float64}) + stress::Symm2 = zero(Symm2{Float64}) + plastic_strain::Symm2 = zero(Symm2{Float64}) + cumeq::Float64 = zero(Float64) + jacobian::Symm4 = zero(Symm4{Float64}) end @with_kw mutable struct IdealPlastic <: AbstractMaterial - drivers :: IdealPlasticDriverState = IdealPlasticDriverState() - ddrivers :: IdealPlasticDriverState = IdealPlasticDriverState() - variables :: IdealPlasticVariableState = IdealPlasticVariableState() - variables_new :: IdealPlasticVariableState = IdealPlasticVariableState() - parameters :: IdealPlasticParameterState = IdealPlasticParameterState() - dparameters :: IdealPlasticParameterState = IdealPlasticParameterState() + drivers::IdealPlasticDriverState = IdealPlasticDriverState() + ddrivers::IdealPlasticDriverState = IdealPlasticDriverState() + variables::IdealPlasticVariableState = IdealPlasticVariableState() + variables_new::IdealPlasticVariableState = IdealPlasticVariableState() + parameters::IdealPlasticParameterState = IdealPlasticParameterState() + dparameters::IdealPlasticParameterState = IdealPlasticParameterState() end """ From f5daa58fb4bba05d93af1204aae38dbe3f9c77f5 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 5 Jun 2020 17:47:16 +0300 Subject: [PATCH 17/66] Unify API and implementation of viscoplastic.jl with the other models --- src/viscoplastic.jl | 289 ++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 155 deletions(-) diff --git a/src/viscoplastic.jl b/src/viscoplastic.jl index bf3d9cd..48f2566 100644 --- a/src/viscoplastic.jl +++ b/src/viscoplastic.jl @@ -5,70 +5,37 @@ # http://www.solid.iei.liu.se/Education/TMHL55/TMHL55_lp1_2010/lecture_notes/plasticity_flow_rule_isotropic_hardening.pdf # http://mms.ensmp.fr/msi_paris/transparents/Georges_Cailletaud/2013-GC-plas3D.pdf -using LinearAlgebra # in Julia 1.0+, the I matrix lives here. -using NLsolve +POTENTIAL_FUNCTIONS = [:norton, :bingham] -POTENTIAL_FUNCTIONS = [:norton] +@with_kw mutable struct ViscoPlasticDriverState <: AbstractMaterialState + time::Float64 = zero(Float64) + strain::Symm2 = zero(Symm2{Float64}) +end -# TODO: To conform with the API of the other models, viscoplastic still needs: -# - drivers, ddrivers, variables, variables_new, parameters, dparameters -# - ViscoPlasticDriverState: maybe time, strain? -# - ViscoPlasticParameterState: youngs_modulus, poissons_ratio, yield_stress, potential, params -# - ViscoPlasticVariableState: stress, plastic_strain, cumeq, jacobian -@with_kw mutable struct ViscoPlastic <: AbstractMaterial - # Material parameters +@with_kw struct ViscoPlasticParameterState <: AbstractMaterialState youngs_modulus::Float64 = zero(Float64) poissons_ratio::Float64 = zero(Float64) yield_stress::Float64 = zero(Float64) + # Settings for the plastic potential. potential::Symbol = :norton - # Parameters for potential. If potential=:norton, this is [K, n]. - params::Vector{Float64} = [180.0e3, 0.92] - # Internal state variables - plastic_strain::Vector{Float64} = zeros(6) # TODO: never used? - dplastic_strain::Vector{Float64} = zeros(6) - plastic_multiplier::Float64 = zero(Float64) # TODO: never used, remove? - dplastic_multiplier::Float64 = zero(Float64) # TODO: never used, remove? - # https://mauro3.github.io/Parameters.jl/dev/manual/ - @assert potential in POTENTIAL_FUNCTIONS -end - -# TODO: Rewrite this module to use regular tensor notation. -# Could then just use the functions from the Tensors package. -"""Double contraction of rank-2 tensors stored in an array format.""" -function dcontract(x::A, y::A) where A <: AbstractArray{<:Number, 2} - return sum(x .* y) + params::Vector{Float64} = [180.0e3, 0.92] # If potential=:norton, this is [K, n]. + @assert potential in POTENTIAL_FUNCTIONS # https://mauro3.github.io/Parameters.jl/dev/manual/ end -"""Deviatoric part of rank-2 tensor. - -Input in Voigt format. Output as a 3×3 array`. - -Order of components is: - - t = [v[1] v[4] v[6]; - v[4] v[2] v[5]; - v[6] v[5] v[3]] -""" -function dev(v::AbstractVector{<:Number}) - t = [v[1] v[4] v[6]; - v[4] v[2] v[5]; - v[6] v[5] v[3]] - # TODO: Unify storage order with the Julia standard: - # fromvoigt(SymmetricTensor{2,3}, Array(1:6)) - # - # 1 6 5 - # 6 2 4 - # 5 4 3 - # - # Changing this affects how to read sol_mat in dNortondStress. - return t - 1/3 * tr(t) * I +@with_kw struct ViscoPlasticVariableState <: AbstractMaterialState + stress::Symm2 = zero(Symm2{Float64}) + plastic_strain::Symm2 = zero(Symm2{Float64}) + cumeq::Float64 = zero(Float64) + jacobian::Symm4 = zero(Symm4{Float64}) end -"""Equivalent stress.""" -function equivalent_stress(stress::AbstractVector{<:Number}) - s = dev(stress) - J_2 = 1/2 * dcontract(s,s) - return sqrt(3 * J_2) +@with_kw mutable struct ViscoPlastic <: AbstractMaterial + drivers::ViscoPlasticDriverState = ViscoPlasticDriverState() + ddrivers::ViscoPlasticDriverState = ViscoPlasticDriverState() + variables::ViscoPlasticVariableState = ViscoPlasticVariableState() + variables_new::ViscoPlasticVariableState = ViscoPlasticVariableState() + parameters::ViscoPlasticParameterState = ViscoPlasticParameterState() + dparameters::ViscoPlasticParameterState = ViscoPlasticParameterState() end """Norton rule. @@ -80,9 +47,8 @@ end `stress` is passed through to `f` as its only argument, so its storage format must be whatever `f` expects. """ -function norton_plastic_potential(stress, params::AbstractVector{<:Number}, f::Function) - K = params[1] - n = params[2] +function norton_plastic_potential(stress::Symm2{<:Number}, params::AbstractVector{<:Number}, f::Function) + K, n = params return K/(n+1) * (f(stress) / K)^(n + 1) end @@ -92,115 +58,128 @@ end `f` and `stress` like in `norton_plastic_potential`. """ -function bingham_plastic_potential(stress, params::AbstractVector{<:Number}, f::Function) +function bingham_plastic_potential(stress::Symm2{<:Number}, params::AbstractVector{<:Number}, f::Function) eta = params[1] return 0.5 * (f(stress) / eta)^2 end -"""Jacobian of the Norton rule.""" -function dNortondStress(stress::AbstractVector{<:Number}, params::AbstractVector{<:Number}, f::Function) - # using SymPy - # @vars K n s - # @symfuns f - # norton = K / (n + 1) * (f(s) / K)^(n + 1) - # simplify(diff(norton, s)) - # --> (f(s) / K)^n df/ds - # - # ...so this only works if `f` is the von Mises yield function. - # - # K = params[1] - # n = params[2] - # stress_v = equivalent_stress(stress) - # stress_dev = dev(stress) - # sol_mat = (f(stress) / K)^n * 3.0/2.0 * stress_dev / stress_v - # return [sol_mat[1,1], sol_mat[2,2], sol_mat[3,3], - # 2*sol_mat[1,2], 2*sol_mat[2,3], 2*sol_mat[1,3]] - - # Instead, let's use AD: - pot(stress) = norton_plastic_potential(stress, params, f) - return ForwardDiff.jacobian(pot, stress) -end - -function integrate_material!(material::ViscoPlastic) - mat = material.properties - - E = mat.youngs_modulus - nu = mat.poissons_ratio - R0 = mat.yield_stress - potential = mat.potential - params = mat.params - lambda, mu = lame(E, nu) +""" + state_to_vector(sigma::Symm2{<:Real}) - # TODO: these fields don't exist! - stress = material.stress - strain = material.strain - dstress = material.dstress - dstrain = material.dstrain - dt = material.dtime - # D = material.jacobian # TODO: this field doesn't exist! - - dedt = dstrain ./ dt - - # This looks like a rank-4 tensor in a Voigt matrix format...? - # fill!(D, 0.0) - # D[1,1] = D[2,2] = D[3,3] = 2.0*mu + lambda - # D[4,4] = D[5,5] = D[6,6] = mu - # D[1,2] = D[2,1] = D[2,3] = D[3,2] = D[1,3] = D[3,1] = lambda - # - # Let's find out. In the REPL: - # S = SymmetricTensor{2, 3}(1:6) - # tovoigt(S) - # So the index mapping is: - # Voigt rank-2 - # 1 11 - # 2 22 - # 3 33 - # 4 23 - # 5 13 - # 6 12 - # So this is just the isotropic elasticity tensor C: - # Cijkl = λ δij δkl + μ (δik δjl + δil δjk) - # with the strain stored in a Voigt format: - # [εxx, εyy, εzz, εyz, εxz, εxy] (ordering of shear components doesn't matter here) - # - # So let's just: - D = tovoigt(isotropic_elasticity_tensor(lambda, mu)) - - """von Mises yield function.""" - function f(stress::AbstractVector{<:Number}) # stress in Voigt format - return equivalent_stress(stress) - R0 - end +Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. +""" +@inline function state_to_vector(sigma::Symm2{<:Real}) + return [tovoigt(sigma); 0.0] # The extra zero is padding to make the input/output shapes of g! match. +end - dstress[:] .= D * dedt .* dt - stress_elastic_trial = stress + dstress +""" + state_from_vector(x::AbstractVector{S}) where S <: Real - if f(stress_elastic_trial) <= 0 # not in plastic region - fill!(mat.dplastic_strain, 0.0) - return nothing - end +Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. +""" +@inline function state_from_vector(x::AbstractVector{S}) where S <: Real + sigma = fromvoigt(Symm2{S}, @view x[1:6]) + # The padding (component 7) is unused, so we just ignore it here. + return sigma +end - if potential == :norton - pot(stress) -> norton_plastic_potential(stress, params, f) - else - @assert false # cannot happen (unless someone mutates the struct contents) - end +""" + integrate_material!(material::ViscoPlastic) - dpotdstress(stress) = ForwardDiff.jacobian(pot, stress) +Viscoplastic material, with switchable plastic potential. No hardening. +""" +function integrate_material!(material::ViscoPlastic) + p = material.parameters + v = material.variables + dd = material.ddrivers + d = material.drivers + + E = p.youngs_modulus + nu = p.poissons_ratio + R0 = p.yield_stress + lambda, mu = lame(E, nu) - # The nonlinear equation system, (stuff) = 0 - function g!(F, x) - dsigma = x[1:6] - F[1:6] = dsigma - D * (dedt - dpotdstress(stress + dsigma)) .* dt - F[end] = f(stress + dsigma) + @unpack strain, time = d + dstrain = material.ddrivers.strain + dtime = material.ddrivers.time + @unpack stress, plastic_strain, cumeq = v + + # elastic part + jacobian = isotropic_elasticity_tensor(lambda, mu) # dσ/dε, i.e. ∂σij/∂εkl + stress += dcontract(jacobian, dstrain) # add the elastic stress increment, get the elastic trial stress + + f(sigma) = sqrt(1.5)*norm(dev(sigma)) - R0 # von Mises yield function + + if f(stress) > 0 + params = mat.parameters.params # parameters for the plastic potential function + + if mat.parameters.potential == :norton + psi(sigma) -> norton_plastic_potential(sigma, params, f) + elseif mat.parameters.potential == :bingham + psi(sigma) -> bingham_plastic_potential(sigma, params, f) + else + @assert false # cannot happen + end + + dpsi_dsigma(sigma) = ForwardDiff.jacobian(psi, sigma) + + # Compute the residual. F is output, x is filled by NLsolve. + # The solution is x = x* such that g(x*) = 0. + function g!(F, x) + dsigma = state_from_vector(x) # tentative new value from nlsolve + # Evolution equation in delta form: + # + # Δσ = D : (Δε - (∂ψ/∂σ)(σ_new) Δt) + # + # where + # + # Δσ ≡ σ_new - σ_old + # + # Then move terms to get the standard form, (stuff) = 0. + # + sigma_new = stress + dsigma + F[1:6] = dsigma - dcontract(jacobian, (dstrain - (dpsi_dsigma(sigma_new) * dtime))) + # Constraint: the solution is on the yield surface: + # f(σ_new) = 0 + F[end] = f(sigma_new) + end + + x0 = state_to_vector(dstress) + res = nlsolve(g!, x0, autodiff=:forward) + converged(res) || error("Nonlinear system of equations did not converge!") + dsigma = state_from_vector(res.zero) + + stress += dsigma + + plastic_dstrain = dpsi_dsigma(stress) + dp = norm(plastic_dstrain) # TODO: verify. Do we need to divide by sqrt(1.5) to match the other models? + + plastic_strain += plastic_dstrain + cumeq += dp # cumulative equivalent plastic strain (note dp ≥ 0) + + # TODO: Verify. This comes from chaboche.jl, but the algorithm looks general enough. Comments updated. + # + # TODO: I still don't see where the minus sign comes from. Using the chain rule + # TODO: and the inverse function theorem, there should be no minus sign. + # + # Compute the new Jacobian, accounting for the plastic contribution. Because + # x ≡ [σ 0] + # we have + # (dx/dε)[1:6,1:6] = dσ/dε + # for which we can compute the LHS as follows: + # dx/dε = dx/dr dr/dε = inv(dr/dx) dr/dε ≡ (dr/dx) \ (dr/dε) + # where r = r(x) is the residual, given by the function g!. AD can get us dr/dx automatically, + # the other factor we will have to supply manually. + drdx = ForwardDiff.jacobian(debang(g!), x) # Array{7, 7} + drde = zeros((length(x),6)) # Array{7, 6} + drde[1:6, 1:6] = -tovoigt(jacobian) # (negative of the) elastic Jacobian. Follows from the defn. of g!. + jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end - x0 = vec([dstress; 0.0]) - res = nlsolve(g!, x0, autodiff=:forward) - converged(res) || error("Nonlinear system of equations did not converge!") - dsigma = res.zero[1:6] - - dstrain_plastic = dpotdstress(stress + dsigma) - mat.dplastic_strain = dstrain_plastic - dstress[:] .= D * (dedt - dstrain_plastic) .* dt + variables_new = ViscoPlasticVariableState(stress = stress, + plastic_strain = plastic_strain, + cumeq = cumeq, + jacobian = jacobian) + material.variables_new = variables_new return nothing end From ac68c00e24e70e9ed4df0afc651a7d0c18733446 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 8 Jun 2020 13:05:01 +0300 Subject: [PATCH 18/66] Delete outdated implementation of the viscoplastic model. Chaboche reduces to the viscoplastic model as C1 = C2 = 0, so we don't need a separate implementation for it. --- src/viscoplastic.jl | 185 -------------------------------------------- 1 file changed, 185 deletions(-) delete mode 100644 src/viscoplastic.jl diff --git a/src/viscoplastic.jl b/src/viscoplastic.jl deleted file mode 100644 index 48f2566..0000000 --- a/src/viscoplastic.jl +++ /dev/null @@ -1,185 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -# Viscoplastic material. See: -# http://www.solid.iei.liu.se/Education/TMHL55/TMHL55_lp1_2010/lecture_notes/plasticity_flow_rule_isotropic_hardening.pdf -# http://mms.ensmp.fr/msi_paris/transparents/Georges_Cailletaud/2013-GC-plas3D.pdf - -POTENTIAL_FUNCTIONS = [:norton, :bingham] - -@with_kw mutable struct ViscoPlasticDriverState <: AbstractMaterialState - time::Float64 = zero(Float64) - strain::Symm2 = zero(Symm2{Float64}) -end - -@with_kw struct ViscoPlasticParameterState <: AbstractMaterialState - youngs_modulus::Float64 = zero(Float64) - poissons_ratio::Float64 = zero(Float64) - yield_stress::Float64 = zero(Float64) - # Settings for the plastic potential. - potential::Symbol = :norton - params::Vector{Float64} = [180.0e3, 0.92] # If potential=:norton, this is [K, n]. - @assert potential in POTENTIAL_FUNCTIONS # https://mauro3.github.io/Parameters.jl/dev/manual/ -end - -@with_kw struct ViscoPlasticVariableState <: AbstractMaterialState - stress::Symm2 = zero(Symm2{Float64}) - plastic_strain::Symm2 = zero(Symm2{Float64}) - cumeq::Float64 = zero(Float64) - jacobian::Symm4 = zero(Symm4{Float64}) -end - -@with_kw mutable struct ViscoPlastic <: AbstractMaterial - drivers::ViscoPlasticDriverState = ViscoPlasticDriverState() - ddrivers::ViscoPlasticDriverState = ViscoPlasticDriverState() - variables::ViscoPlasticVariableState = ViscoPlasticVariableState() - variables_new::ViscoPlasticVariableState = ViscoPlasticVariableState() - parameters::ViscoPlasticParameterState = ViscoPlasticParameterState() - dparameters::ViscoPlasticParameterState = ViscoPlasticParameterState() -end - -"""Norton rule. - -`params = [K, n]`. - -`f` is the yield function. - -`stress` is passed through to `f` as its only argument, -so its storage format must be whatever `f` expects. -""" -function norton_plastic_potential(stress::Symm2{<:Number}, params::AbstractVector{<:Number}, f::Function) - K, n = params - return K/(n+1) * (f(stress) / K)^(n + 1) -end - -"""Bingham rule. - -`params = [eta]`. - -`f` and `stress` like in `norton_plastic_potential`. -""" -function bingham_plastic_potential(stress::Symm2{<:Number}, params::AbstractVector{<:Number}, f::Function) - eta = params[1] - return 0.5 * (f(stress) / eta)^2 -end - -""" - state_to_vector(sigma::Symm2{<:Real}) - -Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. -""" -@inline function state_to_vector(sigma::Symm2{<:Real}) - return [tovoigt(sigma); 0.0] # The extra zero is padding to make the input/output shapes of g! match. -end - -""" - state_from_vector(x::AbstractVector{S}) where S <: Real - -Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. -""" -@inline function state_from_vector(x::AbstractVector{S}) where S <: Real - sigma = fromvoigt(Symm2{S}, @view x[1:6]) - # The padding (component 7) is unused, so we just ignore it here. - return sigma -end - -""" - integrate_material!(material::ViscoPlastic) - -Viscoplastic material, with switchable plastic potential. No hardening. -""" -function integrate_material!(material::ViscoPlastic) - p = material.parameters - v = material.variables - dd = material.ddrivers - d = material.drivers - - E = p.youngs_modulus - nu = p.poissons_ratio - R0 = p.yield_stress - lambda, mu = lame(E, nu) - - @unpack strain, time = d - dstrain = material.ddrivers.strain - dtime = material.ddrivers.time - @unpack stress, plastic_strain, cumeq = v - - # elastic part - jacobian = isotropic_elasticity_tensor(lambda, mu) # dσ/dε, i.e. ∂σij/∂εkl - stress += dcontract(jacobian, dstrain) # add the elastic stress increment, get the elastic trial stress - - f(sigma) = sqrt(1.5)*norm(dev(sigma)) - R0 # von Mises yield function - - if f(stress) > 0 - params = mat.parameters.params # parameters for the plastic potential function - - if mat.parameters.potential == :norton - psi(sigma) -> norton_plastic_potential(sigma, params, f) - elseif mat.parameters.potential == :bingham - psi(sigma) -> bingham_plastic_potential(sigma, params, f) - else - @assert false # cannot happen - end - - dpsi_dsigma(sigma) = ForwardDiff.jacobian(psi, sigma) - - # Compute the residual. F is output, x is filled by NLsolve. - # The solution is x = x* such that g(x*) = 0. - function g!(F, x) - dsigma = state_from_vector(x) # tentative new value from nlsolve - # Evolution equation in delta form: - # - # Δσ = D : (Δε - (∂ψ/∂σ)(σ_new) Δt) - # - # where - # - # Δσ ≡ σ_new - σ_old - # - # Then move terms to get the standard form, (stuff) = 0. - # - sigma_new = stress + dsigma - F[1:6] = dsigma - dcontract(jacobian, (dstrain - (dpsi_dsigma(sigma_new) * dtime))) - # Constraint: the solution is on the yield surface: - # f(σ_new) = 0 - F[end] = f(sigma_new) - end - - x0 = state_to_vector(dstress) - res = nlsolve(g!, x0, autodiff=:forward) - converged(res) || error("Nonlinear system of equations did not converge!") - dsigma = state_from_vector(res.zero) - - stress += dsigma - - plastic_dstrain = dpsi_dsigma(stress) - dp = norm(plastic_dstrain) # TODO: verify. Do we need to divide by sqrt(1.5) to match the other models? - - plastic_strain += plastic_dstrain - cumeq += dp # cumulative equivalent plastic strain (note dp ≥ 0) - - # TODO: Verify. This comes from chaboche.jl, but the algorithm looks general enough. Comments updated. - # - # TODO: I still don't see where the minus sign comes from. Using the chain rule - # TODO: and the inverse function theorem, there should be no minus sign. - # - # Compute the new Jacobian, accounting for the plastic contribution. Because - # x ≡ [σ 0] - # we have - # (dx/dε)[1:6,1:6] = dσ/dε - # for which we can compute the LHS as follows: - # dx/dε = dx/dr dr/dε = inv(dr/dx) dr/dε ≡ (dr/dx) \ (dr/dε) - # where r = r(x) is the residual, given by the function g!. AD can get us dr/dx automatically, - # the other factor we will have to supply manually. - drdx = ForwardDiff.jacobian(debang(g!), x) # Array{7, 7} - drde = zeros((length(x),6)) # Array{7, 6} - drde[1:6, 1:6] = -tovoigt(jacobian) # (negative of the) elastic Jacobian. Follows from the defn. of g!. - jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) - end - - variables_new = ViscoPlasticVariableState(stress = stress, - plastic_strain = plastic_strain, - cumeq = cumeq, - jacobian = jacobian) - material.variables_new = variables_new - return nothing -end From 255d9a18bf9cabd107166dd4301a9b46aa41fc3a Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 8 Jun 2020 14:41:43 +0300 Subject: [PATCH 19/66] Remove outdated viscoplastic tests, too --- test/test_viscoplastic.jl | 61 -------------- test/test_viscoplastic/plot_material.jl | 107 ------------------------ 2 files changed, 168 deletions(-) delete mode 100644 test/test_viscoplastic.jl delete mode 100644 test/test_viscoplastic/plot_material.jl diff --git a/test/test_viscoplastic.jl b/test/test_viscoplastic.jl deleted file mode 100644 index 29bdf11..0000000 --- a/test/test_viscoplastic.jl +++ /dev/null @@ -1,61 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -using Materials, Test, ForwardDiff - - -@testset "Calculate plastic strain" begin - # time = [...] - # strain = [...] - # stress = run_simulation(mat, time, strain) - # @test isapprox(stress, stress_expected) - - n = 1.0 - K = 100.0 - - mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) - mat.properties.youngs_modulus = 200.0e3 - mat.properties.poissons_ratio = 0.3 - mat.properties.yield_stress = 100.0 - - mat.dtime = 0.25 - mat.dstrain .= 1.0e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]*mat.dtime - integrate_material!(mat) - @test isapprox(mat.stress+mat.dstress, [50.0, 0.0, 00.0, 0.0, 0.0, 0.0]) - mat.time += mat.dtime - mat.strain .+= mat.dstrain - mat.stress .+= mat.dstress - mat.properties.plastic_strain .+= mat.properties.dplastic_strain - mat.properties.plastic_multiplier += mat.properties.dplastic_multiplier - - - integrate_material!(mat) - @test isapprox(mat.stress+mat.dstress, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - @test isapprox(mat.properties.dplastic_multiplier, 0.0; atol=1.0e-12) - - mat.time += mat.dtime - mat.strain .+= mat.dstrain - mat.stress .+= mat.dstress - mat.properties.plastic_strain .+= mat.properties.dplastic_strain - mat.properties.plastic_multiplier += mat.properties.dplastic_multiplier - - integrate_material!(mat) - @test mat.properties.dplastic_strain[1] > 0 - @test mat.properties.dplastic_strain[2] < 0 - @test mat.properties.dplastic_strain[3] < 0 - - mat.time += mat.dtime - mat.strain .+= mat.dstrain - mat.stress .+= mat.dstress - mat.properties.plastic_strain .+= mat.properties.dplastic_strain - mat.properties.plastic_multiplier += mat.properties.dplastic_multiplier - - stress = mat.stress - dstress = mat.dstress - - @test Materials.von_mises_yield(stress + dstress, 200) <= 0 -end - -@testset "Invalid potential is not defined" begin - @test_throws ErrorException Material(ViscoPlastic, (potential=:notNorton, params=[0.0, 0.0])) -end diff --git a/test/test_viscoplastic/plot_material.jl b/test/test_viscoplastic/plot_material.jl deleted file mode 100644 index 6f9cb5a..0000000 --- a/test/test_viscoplastic/plot_material.jl +++ /dev/null @@ -1,107 +0,0 @@ -using Materials, Test -using Materials: Simulator -using PyPlot - -num = 100 # Number of steps -E = 200000.0 # Young's modulus [GPa] -poissons_ratio = 0.3 # Poisson's ratio -stress_y = 200.0 # Yield stress -dt = 0.1 -n = 0.92 -K = 180.0e3 - - -# Simulation 1 -mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) -mat.properties.youngs_modulus = E -mat.properties.poissons_ratio = poissons_ratio -mat.properties.yield_stress = stress_y -times = [0.0] -strains = [zeros(6)] - -L = 1000.0 # Initial length [mm] -dL = range(0, stop=3.1, length=num) # Change of length [mm] - -strain = collect(dL / L) -# strain_total = vec([strain; reverse(strain[2:end-1]); -strain; reverse(-strain[2:end-1])]) -strain_total = strain -nsteps = length(strain_total) -dt = 0.03 - -for i=1:nsteps - str = zeros(6) - str[1] = strain_total[i] - str[2] = -strain_total[i] * poissons_ratio - str[3] = -strain_total[i] * poissons_ratio - - push!(times, times[end] + dt) - push!(strains, str) -end - -println(strains[2][1] / dt) -sim = Simulator(mat) -Materials.initialize!(sim, strains, times) -Materials.run!(sim) - -s11s = [s[1] for s in sim.stresses] -e11s = [e[1] for e in strains] - -# Simulation 2 -mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) -mat.properties.youngs_modulus = E -mat.properties.poissons_ratio = poissons_ratio -mat.properties.yield_stress = stress_y -times = [0.0] -strains = [zeros(6)] - -dt = 0.08 - -for i=1:nsteps - str = zeros(6) - str[1] = strain_total[i] - str[2] = -strain_total[i] * poissons_ratio - str[3] = -strain_total[i] * poissons_ratio - - push!(times, times[end] + dt) - push!(strains, str) -end -println(strains[3][1] / dt) -sim = Simulator(mat) -Materials.initialize!(sim, strains, times) -Materials.run!(sim) - -s11s2 = [s[1] for s in sim.stresses] -e11s2 = [e[1] for e in strains] - -# Simulation 3 -mat = Material(ViscoPlastic, (potential=:norton, params=[K, n])) -mat.properties.youngs_modulus = E -mat.properties.poissons_ratio = poissons_ratio -mat.properties.yield_stress = stress_y -times = [0.0] -strains = [zeros(6)] - -dt = 0.5 - -for i=1:nsteps - str = zeros(6) - str[1] = strain_total[i] - str[2] = -strain_total[i] * poissons_ratio - str[3] = -strain_total[i] * poissons_ratio - - push!(times, times[end] + dt) - push!(strains, str) -end -println(strains[3][1] / dt) -sim = Simulator(mat) -Materials.initialize!(sim, strains, times) -Materials.run!(sim) - -s11s3 = [s[1] for s in sim.stresses] -e11s3 = [e[1] for e in strains] -legend(bbox_to_anchor=[1.05,1],loc=2,borderaxespad=0) -plot(e11s, s11s, label="de/dt=0.001346") -plot(e11s2, s11s2, label="de/dt=0.00081") -plot(e11s3, s11s3, label="de/dt=0.000008") -grid() -show() From 0c24a3be9a22d20d21e787f2e3cd8dc7f4dc6d31 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 8 Jun 2020 17:35:24 +0300 Subject: [PATCH 20/66] To me and Reijo it seems the minus must not be there. Removed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The state vector is x ≡ [σ R X1 X2] so we have (dx/dε)[1:6,1:6] = dσ/dε We can get this from dx/dε = dx/dr dr/dε = inv(dr/dx) dr/dε ≡ (dr/dx) \ (dr/dε) where we have used the chain rule and the (multidimensional) inverse function theorem. The minus sign in Joona's original calculation comes from defining the residual as -r, instead of +r, where r is the residual function named g! in the code. This sign difference will of course be compensated by a sign difference in the corresponding dr/dx term (which is automatically computed by ForwardDiff, so its sign is not immediately visible in the source code text), so the value of the material Jacobian dσ/dε - which is the product of the two terms - will be the same in either case. The point is, the sign convention of the manually calculated drde term must match the one used in the definition of g!. A sign mistake here is not caught by testing the behavior of a single material point, because the material Jacobian is only used by FEM calculations. (From the perspective of Materials.jl itself, it's write-only.) --- src/chaboche.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 49fb1b3..2bae8cd 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -116,7 +116,7 @@ function integrate_material!(material::Chaboche) # the other factor we will have to supply manually. drdx = ForwardDiff.jacobian(debang(g!), x) # Array{19, 19} drde = zeros((length(x),6)) # Array{19, 6} - drde[1:6, 1:6] = -tovoigt(jacobian) # (negative of the) elastic Jacobian. Follows from the defn. of g!. + drde[1:6, 1:6] = tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end variables_new = ChabocheVariableState(stress = stress, From f031580d9d818e6dd6907db2aea1a7311c01641a Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 14:17:50 +0300 Subject: [PATCH 21/66] Clean up utilities --- src/utilities.jl | 112 ++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index ecf6474..91319f2 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -7,57 +7,60 @@ const Symm2{T} = SymmetricTensor{2,3,T} """Alias for symmetric tensor type of rank 4, dimension 3.""" const Symm4{T} = SymmetricTensor{4,3,T} -# Various rank-4 unit tensors, for documentation. -# Let A be a rank-2 tensor, and I the rank-2 unit tensor. -# -# typ = Float64 -# II = Symm4{typ}((i,j,k,l) -> delta(i,k)*delta(j,l)) # II : A = A -# IT = Symm4{typ}((i,j,k,l) -> delta(i,l)*delta(j,k)) # IT : A = transpose(A) -# IS = Symm4{typ}((i,j,k,l) -> 0.5*(II + IT)) # symmetric -# IA = Symm4{typ}((i,j,k,l) -> 0.5*(II - IT)) # screw-symmetric -# IV = 1.0/3.0 * Symm4{typ}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric, IV = (1/3) I ⊗ I -# ID = IS - IV # deviatoric +"""Kronecker delta. delta(i, j) = (i == j) ? 1 : 0.""" +delta(i::Integer, j::Integer) = (i == j) ? 1 : 0 + +"""Rank-4 unit tensor, defined by II : A = A for any rank-2 tensor A.""" +II(T::Type=Float64) = Symm4{T}((i,j,k,l) -> delta(i,k)*delta(j,l)) +"""Rank-4 unit tensor, defined by IT : A = transpose(A) for any rank-2 tensor A.""" +IT(T::Type=Float64) = Symm4{T}((i,j,k,l) -> delta(i,l)*delta(j,k)) + +"""Rank-4 unit tensor, symmetric. IS ≡ (1/2) (II + IT).""" +IS(T::Type=Float64) = Symm4{T}((i,j,k,l) -> 1//2 * (II(T) + IT(T))) +"""Rank-4 unit tensor, screw-symmetric. IA ≡ (1/2) (II - IT).""" +IA(T::Type=Float64) = Symm4{T}((i,j,k,l) -> 1//2 * (II(T) - IT(T))) + +"""Rank-4 unit tensor, volumetric. IS ≡ (1/3) I ⊗ I, where I is the rank-2 unit tensor.""" +IV(T::Type=Float64) = Symm4{T}((i,j,k,l) -> 1//3 * delta(i,j)*delta(k,l)) +"""Rank-4 unit tensor, deviatoric. ID ≡ IS - IV.""" +ID(T::Type=Float64) = IS(T) - IV(T) """ - lame(E, nu) + isotropic_elasticity_tensor(lambda, mu) + +Compute the elasticity tensor C(i,j,k,l) (rank 4, symmetric) for an isotropic +material having the Lamé parameters `lambda` and `mu`. -Convert the elastic parameters (E, nu) to the Lamé parameters (lambda, mu). Isotropic material. +If you have (E, nu) instead, use `lame` to get (lambda, mu). """ -@inline function lame(E::Real, nu::Real) - lambda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) - mu = E / (2.0 * (1.0 + nu)) - return lambda, mu -end +isotropic_elasticity_tensor(lambda::T, mu::T) where T <: Real = Symm4{T}((i,j,k,l) -> 3 * lambda * IV(T) + 2 * mu * IS(T)) """ - delame(lambda, mu) + lame(E, nu) -Convert the Lamé parameters (lambda, mu) to the elastic parameters (E, nu). Isotropic material. +Convert elastic parameters (E, nu) of an isotropic material to Lamé parameters (lambda, mu). """ -@inline function delame(lambda::Real, mu::Real) - E = mu * (3.0 * lambda + 2.0 * mu) / (lambda + mu) - nu = lambda / (2.0 * (lambda + mu)) - return E, nu +function lame(E::Real, nu::Real) + lambda = E * nu / ((1 + nu) * (1 - 2 * nu)) + mu = E / (2 * (1 + nu)) + return lambda, mu end """ - isotropic_elasticity_tensor(lambda, mu) - -Compute the elasticity tensor (rank 4, symmetric) for an isotropic material -having the Lamé parameters `lambda` and `mu`. + delame(lambda, mu) -If you have (E, nu) instead, use `lame` to get (lambda, mu). +Convert Lamé parameters (lambda, mu) of an isotropic material to elastic parameters (E, nu). """ -function isotropic_elasticity_tensor(lambda::Real, mu::Real) - delta(i,j) = i==j ? 1.0 : 0.0 - C(i,j,k,l) = lambda*delta(i,j)*delta(k,l) + mu*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k)) - return Symm4{typeof(lambda)}(C) +function delame(lambda::Real, mu::Real) + E = mu * (3 * lambda + 2 * mu) / (lambda + mu) + nu = lambda / (2 * (lambda + mu)) + return E, nu end """ debang(f!, ex=nothing) -Take away the bang; i.e. convert a mutating function into non-mutating form. +Convert a mutating function into non-mutating form. `f!` must be a two-argument mutating function, which writes the result into its first argument. The result of `debang` is then `f`, a single-argument @@ -70,22 +73,23 @@ to supply just `f!`. When `f` is called, output will be allocated as `similar(x) When the type, size and/or shape of `out` are different from those of `x`, then an example instance of the correct type with the correct size and shape for the output must be supplied, as debang's `ex` argument. When `f` is called, output -will be allocated as `similar(ex)`. +will be allocated as `similar(ex)`. The `ex` instance will be automatically kept +alive by the lexical closure of `f`. # Note -While the type of F is known at compile time, the size and shape are typically -runtime properties, not encoded into the type. For example, arrays have the -number of dimensions as part of the type, but the length of each dimension -is only defined at run time, when an instance is created. This is why the `ex` -argument is needed. +While the type of `out` is known at compile time, the size and shape are +typically runtime properties, not encoded into the type. For example, arrays +have the number of dimensions as part of the type, but the length of each +dimension is only defined at run time, when an instance is created. This is why +the `ex` argument is needed. # Etymology By convention, mutating functions are marked with an exclamation mark, a.k.a. -bang. Debanging takes away the bang. +bang. This function takes away the bang. """ -function debang(f!, ex=nothing) +function debang(f!::Function, ex=nothing) if ex === nothing function f(x) out = similar(x) @@ -102,24 +106,30 @@ function debang(f!, ex=nothing) return f end -# This comes from viscoplastic.jl, and is currently unused. -# The original error message suggested this was planned to be used for "radial return". -"""A simple Newton solver for x* such that f(x*) = 0.""" +# This comes from the old viscoplastic.jl, and is currently unused. +# The original wording of the error message suggested this was planned to be used for "radial return". +"""A simple Newton solver for x* such that f(x*) = 0. + +The input `x` is the initial guess. + +The default `dfdx=nothing` uses `ForwardDiff.jacobian` to compute the jacobian +automatically. + +`tol` is measured in the vector norm of the change in `x` between successive +iterations. +""" function find_root(f::Function, x::AbstractVector{<:Real}, dfdx::Union{Function, Nothing}=nothing; - max_iter::Integer=50, norm_acc::Real=1e-9) + max_iter::Integer=50, tol::Real=1e-9) if dfdx === nothing dfdx = (x) -> ForwardDiff.jacobian(f, x) end - converged = false for i=1:max_iter dx = -dfdx(x) \ f(x) x += dx - if norm(dx) < norm_acc - converged = true - break + if norm(dx) < tol + return x end end - converged || error("No convergence!") - return x + error("No convergence!") end From 4fb24f16974ab88b4c2c1c3adb1cfa991a72b0ba Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 15:05:39 +0300 Subject: [PATCH 22/66] Modularize the whole library into submodules. This gives each model implementation its own namespace. Hence, models can now define private symbols (e.g. helper functions, even with conflicting type signatures) without the danger of accidentally stepping on each others' toes. - Each module must now declare its public symbols: `export foo, bar, ...` (explicit is better than implicit) These should have unique names across the whole Materials.jl library, because the public symbols are intended to be re-exported from the top level. - Each module must now `using` or `import` whatever it needs. (explicit is better than implicit) In most cases: using LinearAlgebra, ForwardDiff, Tensors, NLsolve, Parameters import ..AbstractMaterial, ..AbstractMaterialState import ..Utilities: whichever, functionality, you, need import ..integrate_material! # for method extension should cover it. Please be careful to not leave in any unused imports, to make the code easier to read. (Each `using` brings in another package to potentially look up any name in; the fewer, the better to avoid overloading the human reader.) I've left in the `using` statements instead of switching to explicit `import` for cases which introduce obvious mathematical functionality. E.g. out of the list above, it would be silly to expect tensors to live anywhere other than the package named, appropriately enough, `Tensors`. Similarly for `nlsolve`. `fromvoigt` and `tovoigt` are borderline cases, but for anyone familiar with solid mechanics, `Tensors` should be the first guess. To be 100% explicit, these could be imported explicitly. `ForwardDiff.jacobian` should in any case be called by its qualified name, to make it explicit which approach is used to compute the jacobian (since that can be done in many ways). The qualified name makes the choice immediately obvious to the reader. `Parameters` is also a borderline case; without an online search, it's not immediately obvious that a macro named `@with_kw` comes from a package named `Parameters`. This I may still switch to an explicit import. - The top level entry point (Materials.jl) manually re-exports those public symbols I thought would be relevant to have for general use. It's just my guess, everyone should feel free to modify it if needed. This could be revised to re-export all public symbols, by using Reexport.jl, thus leaving the decision of which symbols to expose at the top level to the module authors. --- src/Materials.jl | 20 +++++------ src/chaboche.jl | 16 +++++++-- src/idealplastic.jl | 18 +++++++--- src/increments.jl | 11 ++++++ src/utilities.jl | 81 ++++++++++++++++++++++++++++++++++----------- 5 files changed, 110 insertions(+), 36 deletions(-) diff --git a/src/Materials.jl b/src/Materials.jl index 1a1aeb9..32a3345 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -3,11 +3,12 @@ module Materials -using LinearAlgebra, ForwardDiff, Tensors, NLsolve, Parameters - abstract type AbstractMaterial end abstract type AbstractMaterialState end +export AbstractMaterial, AbstractMaterialState +export integrate_material!, update_material!, reset_material! + """ :+(state::T, dstate::T) where T <: AbstractMaterialState @@ -22,8 +23,6 @@ state. return :(T($(expr...))) end -export AbstractMaterial, AbstractMaterialState - """ integrate_material!(material::M) where M <: AbstractMaterial @@ -74,21 +73,22 @@ function reset_material!(material::M) where M <: AbstractMaterial return nothing end -export integrate_material!, update_material!, reset_material! - include("utilities.jl") -export Symm2, Symm4, lame, delame, isotropic_elasticity_tensor, debang +using .Utilities +export Symm2, Symm4 +export delta, II, IT, IS, IA, IV, ID, isotropic_elasticity_tensor +export lame, delame, debang, find_root include("idealplastic.jl") +using .IdealPlasticModule export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlasticVariableState include("chaboche.jl") +using .ChabocheModule export Chaboche, ChabocheDriverState, ChabocheParameterState, ChabocheVariableState -# include("viscoplastic.jl") -# export ViscoPlastic - include("increments.jl") +using .Increments export uniaxial_increment!, biaxial_increment!, stress_driven_uniaxial_increment! end diff --git a/src/chaboche.jl b/src/chaboche.jl index 2bae8cd..fb4fdb6 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -1,6 +1,16 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE +module ChabocheModule + +using LinearAlgebra, ForwardDiff, Tensors, NLsolve, Parameters + +import ..AbstractMaterial, ..AbstractMaterialState +import ..Utilities: Symm2, Symm4, isotropic_elasticity_tensor, lame, debang +import ..integrate_material! # for method extension + +export Chaboche, ChabocheDriverState, ChabocheParameterState, ChabocheVariableState + @with_kw mutable struct ChabocheDriverState <: AbstractMaterialState time::Float64 = zero(Float64) strain::Symm2 = zero(Symm2{Float64}) @@ -44,7 +54,7 @@ end Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. """ -@inline function state_to_vector(sigma::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real +function state_to_vector(sigma::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real return [tovoigt(sigma), R, tovoigt(X1), tovoigt(X2)] end @@ -53,7 +63,7 @@ end Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. """ -@inline function state_from_vector(x::AbstractVector{S}) where S <: Real +function state_from_vector(x::AbstractVector{S}) where S <: Real sigma = fromvoigt(Symm2{S}, @view x[1:6]) R = x[7] X1 = fromvoigt(Symm2{S}, @view x[8:13]) @@ -209,3 +219,5 @@ function create_nonlinear_system_of_equations(material::Chaboche) end return g! end + +end diff --git a/src/idealplastic.jl b/src/idealplastic.jl index df6dae2..7afb032 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -1,6 +1,16 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE +module IdealPlasticModule + +using LinearAlgebra, ForwardDiff, Tensors, NLsolve, Parameters + +import ..AbstractMaterial, ..AbstractMaterialState +import ..Utilities: Symm2, Symm4, isotropic_elasticity_tensor, IS, ID, lame +import ..integrate_material! # for method extension + +export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlaticVariableState + @with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState time::Float64 = zero(Float64) strain::Symm2 = zero(Symm2{Float64}) @@ -67,11 +77,7 @@ function integrate_material!(material::IdealPlastic) stress -= dcontract(jacobian, dp*n) # Compute ∂σij/∂εkl, accounting for the plastic contribution. - delta(i,j) = i==j ? 1.0 : 0.0 - II = Symm4{Float64}((i,j,k,l) -> 0.5*(delta(i,k)*delta(j,l) + delta(i,l)*delta(j,k))) # symmetric - V = 1.0/3.0 * Symm4{Float64}((i,j,k,l) -> delta(i,j)*delta(k,l)) # volumetric - P = II - V # deviatoric - EE = II + dp/R0 * dcontract(jacobian, 1.5*P - otimes(n,n)) # using the elastic jacobian + EE = IS(Float64) + dp/R0 * dcontract(jacobian, 1.5*ID(Float64) - otimes(n,n)) # using the elastic jacobian ED = dcontract(inv(EE), jacobian) # J = ED - (ED : n) ⊗ (n : ED) / (n : ED : n) jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED)) / dcontract(dcontract(n, ED), n) @@ -82,3 +88,5 @@ function integrate_material!(material::IdealPlastic) jacobian=jacobian) material.variables_new = variables_new end + +end diff --git a/src/increments.jl b/src/increments.jl index c7f4585..3e7ac86 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -1,6 +1,15 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE +module Increments + +import Tensors: tovoigt, fromvoigt + +import ..AbstractMaterial +import ..Utilities: Symm2 + +export optimize_dstrain!, uniaxial_increment!, biaxial_increment!, stress_driven_uniaxial_increment! + # The skeleton of the optimizer is always the same, so we provide it as a # higher-order function. The individual specific optimizer functions # (`update_dstrain!)` only need to define the "meat" of how to update `dstrain`. @@ -142,3 +151,5 @@ function stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11 optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end + +end diff --git a/src/utilities.jl b/src/utilities.jl index 91319f2..cc51e64 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,42 +1,81 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE -"""Alias for symmetric tensor type of rank 2, dimension 3.""" +module Utilities + +using Tensors, ForwardDiff + +export Symm2, Symm4 +export delta, II, IT, IS, IA, IV, ID, isotropic_elasticity_tensor +export lame, delame, debang, find_root + +"""Symm2{T} is an alias for SymmetricTensor{2,3,T}.""" const Symm2{T} = SymmetricTensor{2,3,T} -"""Alias for symmetric tensor type of rank 4, dimension 3.""" +"""Symm4{T} is an alias for SymmetricTensor{4,3,T}.""" const Symm4{T} = SymmetricTensor{4,3,T} -"""Kronecker delta. delta(i, j) = (i == j) ? 1 : 0.""" +""" + delta(i::Integer, j::Integer) + +Kronecker delta, defined by delta(i, j) = (i == j) ? 1 : 0. +""" delta(i::Integer, j::Integer) = (i == j) ? 1 : 0 -"""Rank-4 unit tensor, defined by II : A = A for any rank-2 tensor A.""" +""" + II(T::Type=Float64) + +Rank-4 unit tensor, defined by II : A = A for any rank-2 tensor A. +""" II(T::Type=Float64) = Symm4{T}((i,j,k,l) -> delta(i,k)*delta(j,l)) -"""Rank-4 unit tensor, defined by IT : A = transpose(A) for any rank-2 tensor A.""" + +""" + IT(T::Type=Float64) + +Rank-4 unit tensor, defined by IT : A = transpose(A) for any rank-2 tensor A. +""" IT(T::Type=Float64) = Symm4{T}((i,j,k,l) -> delta(i,l)*delta(j,k)) -"""Rank-4 unit tensor, symmetric. IS ≡ (1/2) (II + IT).""" -IS(T::Type=Float64) = Symm4{T}((i,j,k,l) -> 1//2 * (II(T) + IT(T))) -"""Rank-4 unit tensor, screw-symmetric. IA ≡ (1/2) (II - IT).""" -IA(T::Type=Float64) = Symm4{T}((i,j,k,l) -> 1//2 * (II(T) - IT(T))) +""" + IS(T::Type=Float64) + +Rank-4 unit tensor, symmetric. IS ≡ (1/2) (II + IT). +""" +IS(T::Type=Float64) = 1//2 * (II(T) + IT(T)) + +""" + IA(T::Type=Float64) + +Rank-4 unit tensor, screw-symmetric. IA ≡ (1/2) (II - IT). +""" +IA(T::Type=Float64) = 1//2 * (II(T) - IT(T)) + +""" + IV(T::Type=Float64) -"""Rank-4 unit tensor, volumetric. IS ≡ (1/3) I ⊗ I, where I is the rank-2 unit tensor.""" +Rank-4 unit tensor, volumetric. IS ≡ (1/3) I ⊗ I, where I is the rank-2 unit tensor. +""" IV(T::Type=Float64) = Symm4{T}((i,j,k,l) -> 1//3 * delta(i,j)*delta(k,l)) -"""Rank-4 unit tensor, deviatoric. ID ≡ IS - IV.""" + +""" + ID(T::Type=Float64) + +Rank-4 unit tensor, deviatoric. ID ≡ IS - IV. +""" ID(T::Type=Float64) = IS(T) - IV(T) """ - isotropic_elasticity_tensor(lambda, mu) + isotropic_elasticity_tensor(lambda::T, mu::T) where T <: Real Compute the elasticity tensor C(i,j,k,l) (rank 4, symmetric) for an isotropic material having the Lamé parameters `lambda` and `mu`. If you have (E, nu) instead, use `lame` to get (lambda, mu). """ -isotropic_elasticity_tensor(lambda::T, mu::T) where T <: Real = Symm4{T}((i,j,k,l) -> 3 * lambda * IV(T) + 2 * mu * IS(T)) +isotropic_elasticity_tensor(lambda::T, mu::T) where T <: Real = 3 * lambda * IV(T) + 2 * mu * IS(T) """ - lame(E, nu) + lame(E::Real, nu::Real) Convert elastic parameters (E, nu) of an isotropic material to Lamé parameters (lambda, mu). """ @@ -47,7 +86,7 @@ function lame(E::Real, nu::Real) end """ - delame(lambda, mu) + delame(lambda::Real, mu::Real) Convert Lamé parameters (lambda, mu) of an isotropic material to elastic parameters (E, nu). """ @@ -58,7 +97,7 @@ function delame(lambda::Real, mu::Real) end """ - debang(f!, ex=nothing) + debang(f!::Function, ex=nothing) Convert a mutating function into non-mutating form. @@ -96,19 +135,21 @@ function debang(f!::Function, ex=nothing) f!(out, x) return out end + return f else - function f(x) + # We use a different name to make incremental compilation happy. + function f_with_ex(x) out = similar(ex) f!(out, x) return out end + return f_with_ex end - return f end # This comes from the old viscoplastic.jl, and is currently unused. # The original wording of the error message suggested this was planned to be used for "radial return". -"""A simple Newton solver for x* such that f(x*) = 0. +"""A simple Newton solver for the vector x* such that f(x*) = 0. The input `x` is the initial guess. @@ -133,3 +174,5 @@ function find_root(f::Function, x::AbstractVector{<:Real}, end error("No convergence!") end + +end From 849259169af59f3d866b178e67a05ec8865774c2 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 15:32:34 +0300 Subject: [PATCH 23/66] Clean up type signatures Style guide: don't overuse type parameters. --- src/Materials.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Materials.jl b/src/Materials.jl index 32a3345..77b770b 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -24,7 +24,7 @@ state. end """ - integrate_material!(material::M) where M <: AbstractMaterial + integrate_material!(material::AbstractMaterial) Integrate one timestep. The input `material.variables` represents the old problem state. @@ -41,7 +41,7 @@ function integrate_material!(material::M) where M <: AbstractMaterial end """ - update_material!(material::M) where M <: AbstractMaterial + update_material!(material::AbstractMaterial) Commit the result of `integrate_material!`. @@ -49,7 +49,7 @@ In `material`, we add `ddrivers` into `drivers`, `dparameters` into `parameters`, and replace `variables` by `variables_new`. Then we automatically invoke `reset_material!`. """ -function update_material!(material::M) where M <: AbstractMaterial +function update_material!(material::AbstractMaterial) material.drivers += material.ddrivers material.parameters += material.dparameters material.variables = material.variables_new @@ -58,7 +58,7 @@ function update_material!(material::M) where M <: AbstractMaterial end """ - reset_material!(material::M) where M <: AbstractMaterial + reset_material!(material::AbstractMaterial) In `material`, we zero out `ddrivers`, `dparameters` and `variables_new`. This clears out the tentative state produced when a timestep has been computed, but @@ -66,7 +66,7 @@ has not yet been committed. Used internally by `update_material!`. """ -function reset_material!(material::M) where M <: AbstractMaterial +function reset_material!(material::AbstractMaterial) material.ddrivers = typeof(material.ddrivers)() material.dparameters = typeof(material.dparameters)() material.variables_new = typeof(material.variables_new)() From 21d4a03f0d7a00d8fe0daae025e59ffda496b237 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 16:20:29 +0300 Subject: [PATCH 24/66] Make materials generic on the number type Type inference will create instances using Float64 when actually instantiated, because the parameter values are floats. --- src/chaboche.jl | 101 +++++++++++++++++++++++++++----------------- src/idealplastic.jl | 44 ++++++++++--------- src/increments.jl | 4 +- src/utilities.jl | 7 ++- 4 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index fb4fdb6..e049de8 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -11,50 +11,73 @@ import ..integrate_material! # for method extension export Chaboche, ChabocheDriverState, ChabocheParameterState, ChabocheVariableState -@with_kw mutable struct ChabocheDriverState <: AbstractMaterialState - time::Float64 = zero(Float64) - strain::Symm2 = zero(Symm2{Float64}) +@with_kw mutable struct ChabocheDriverState{T <: Real} <: AbstractMaterialState + time::T = zero(T) + strain::Symm2{T} = zero(Symm2{T}) end -@with_kw struct ChabocheParameterState <: AbstractMaterialState - E::Float64 = 0.0 - nu::Float64 = 0.0 - R0::Float64 = 0.0 - Kn::Float64 = 0.0 - nn::Float64 = 0.0 - C1::Float64 = 0.0 - D1::Float64 = 0.0 - C2::Float64 = 0.0 - D2::Float64 = 0.0 - Q::Float64 = 0.0 - b::Float64 = 0.0 +# TODO: complete this docstring +"""Parameter state for Chaboche material. + +`E`: Young's modulus +`nu`: Poisson's ratio +`R0`: initial yield strength +`Kn`: plasticity multiplier divisor +`nn`: plasticity multiplier exponent +`C1`, `D1`: parameters governing behavior of backstress X1 +`C2`, `D2`: parameters governing behavior of backstress X2 +`Q`: shift parameter for yield strength evolution +`b`: multiplier for yield strength evolution +""" +@with_kw struct ChabocheParameterState{T <: Real} <: AbstractMaterialState + E::T = 0 + nu::T = 0 + R0::T = 0 + Kn::T = 0 + nn::T = 0 + C1::T = 0 + D1::T = 0 + C2::T = 0 + D2::T = 0 + Q::T = 0 + b::T = 0 end -@with_kw struct ChabocheVariableState <: AbstractMaterialState - stress::Symm2 = zero(Symm2{Float64}) - X1::Symm2 = zero(Symm2{Float64}) - X2::Symm2 = zero(Symm2{Float64}) - plastic_strain::Symm2 = zero(Symm2{Float64}) - cumeq::Float64 = zero(Float64) - R::Float64 = zero(Float64) - jacobian::Symm4 = zero(Symm4{Float64}) +"""Problem state for Chaboche material. + +`stress`: stress tensor +`X1`: backstress 1 +`X2`: backstress 2 +`plastic_strain`: plastic part of strain tensor +`cumeq`: cumulative equivalent plastic strain (scalar, ≥ 0) +`R`: yield strength +`jacobian`: ∂σij/∂εkl +""" +@with_kw struct ChabocheVariableState{T <: Real} <: AbstractMaterialState + stress::Symm2{T} = zero(Symm2{T}) + X1::Symm2{T} = zero(Symm2{T}) + X2::Symm2{T} = zero(Symm2{T}) + plastic_strain::Symm2{T} = zero(Symm2{T}) + cumeq::T = zero(T) + R::T = zero(T) + jacobian::Symm4{T} = zero(Symm4{T}) end -@with_kw mutable struct Chaboche <: AbstractMaterial - drivers::ChabocheDriverState = ChabocheDriverState() - ddrivers::ChabocheDriverState = ChabocheDriverState() - variables::ChabocheVariableState = ChabocheVariableState() - variables_new::ChabocheVariableState = ChabocheVariableState() - parameters::ChabocheParameterState = ChabocheParameterState() - dparameters::ChabocheParameterState = ChabocheParameterState() +@with_kw mutable struct Chaboche{T <: Real} <: AbstractMaterial + drivers::ChabocheDriverState{T} = ChabocheDriverState{T}() + ddrivers::ChabocheDriverState{T} = ChabocheDriverState{T}() + variables::ChabocheVariableState{T} = ChabocheVariableState{T}() + variables_new::ChabocheVariableState{T} = ChabocheVariableState{T}() + parameters::ChabocheParameterState{T} = ChabocheParameterState{T}() + dparameters::ChabocheParameterState{T} = ChabocheParameterState{T}() end """ - state_to_vector(sigma::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real + state_to_vector(sigma::U, R::T, X1::U, X2::U) where U <: Symm2{T} where T <: Real Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. """ -function state_to_vector(sigma::T, R::S, X1::T, X2::T) where T <: Symm2{S} where S <: Real +function state_to_vector(sigma::U, R::T, X1::U, X2::U) where U <: Symm2{T} where T <: Real return [tovoigt(sigma), R, tovoigt(X1), tovoigt(X2)] end @@ -63,20 +86,20 @@ end Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. """ -function state_from_vector(x::AbstractVector{S}) where S <: Real - sigma = fromvoigt(Symm2{S}, @view x[1:6]) +function state_from_vector(x::AbstractVector{T}) where T <: Real + sigma = fromvoigt(Symm2{T}, @view x[1:6]) R = x[7] - X1 = fromvoigt(Symm2{S}, @view x[8:13]) - X2 = fromvoigt(Symm2{S}, @view x[14:19]) + X1 = fromvoigt(Symm2{T}, @view x[8:13]) + X2 = fromvoigt(Symm2{T}, @view x[14:19]) return sigma, R, X1, X2 end """ - integrate_material!(material::Chaboche) + integrate_material!(material::Chaboche{T}) where T <: Real Chaboche material with two backstresses. Both kinematic and isotropic hardening. """ -function integrate_material!(material::Chaboche) +function integrate_material!(material::Chaboche{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers @@ -164,7 +187,7 @@ X2 are encoded in Voigt format. The function `g!` is intended to be handed over to `nlsolve`. """ -function create_nonlinear_system_of_equations(material::Chaboche) +function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers diff --git a/src/idealplastic.jl b/src/idealplastic.jl index 7afb032..681e65a 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -11,31 +11,31 @@ import ..integrate_material! # for method extension export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlaticVariableState -@with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState - time::Float64 = zero(Float64) - strain::Symm2 = zero(Symm2{Float64}) +@with_kw mutable struct IdealPlasticDriverState{T <: Real} <: AbstractMaterialState + time::T = zero(T) + strain::Symm2{T} = zero(Symm2{T}) end -@with_kw struct IdealPlasticParameterState <: AbstractMaterialState - youngs_modulus::Float64 = zero(Float64) - poissons_ratio::Float64 = zero(Float64) - yield_stress::Float64 = zero(Float64) +@with_kw struct IdealPlasticParameterState{T <: Real} <: AbstractMaterialState + youngs_modulus::T = zero(T) + poissons_ratio::T = zero(T) + yield_stress::T = zero(T) end -@with_kw struct IdealPlasticVariableState <: AbstractMaterialState - stress::Symm2 = zero(Symm2{Float64}) - plastic_strain::Symm2 = zero(Symm2{Float64}) - cumeq::Float64 = zero(Float64) - jacobian::Symm4 = zero(Symm4{Float64}) +@with_kw struct IdealPlasticVariableState{T <: Real} <: AbstractMaterialState + stress::Symm2{T} = zero(Symm2{T}) + plastic_strain::Symm2{T} = zero(Symm2{T}) + cumeq::T = zero(T) + jacobian::Symm4{T} = zero(Symm4{T}) end -@with_kw mutable struct IdealPlastic <: AbstractMaterial - drivers::IdealPlasticDriverState = IdealPlasticDriverState() - ddrivers::IdealPlasticDriverState = IdealPlasticDriverState() - variables::IdealPlasticVariableState = IdealPlasticVariableState() - variables_new::IdealPlasticVariableState = IdealPlasticVariableState() - parameters::IdealPlasticParameterState = IdealPlasticParameterState() - dparameters::IdealPlasticParameterState = IdealPlasticParameterState() +@with_kw mutable struct IdealPlastic{T <: Real} <: AbstractMaterial + drivers::IdealPlasticDriverState{T} = IdealPlasticDriverState{T}() + ddrivers::IdealPlasticDriverState{T} = IdealPlasticDriverState{T}() + variables::IdealPlasticVariableState{T} = IdealPlasticVariableState{T}() + variables_new::IdealPlasticVariableState{T} = IdealPlasticVariableState{T}() + parameters::IdealPlasticParameterState{T} = IdealPlasticParameterState{T}() + dparameters::IdealPlasticParameterState{T} = IdealPlasticParameterState{T}() end """ @@ -44,7 +44,7 @@ end Ideal plastic material: no hardening. The elastic region remains centered on the origin, and retains its original size. """ -function integrate_material!(material::IdealPlastic) +function integrate_material!(material::IdealPlastic{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers @@ -77,7 +77,8 @@ function integrate_material!(material::IdealPlastic) stress -= dcontract(jacobian, dp*n) # Compute ∂σij/∂εkl, accounting for the plastic contribution. - EE = IS(Float64) + dp/R0 * dcontract(jacobian, 1.5*ID(Float64) - otimes(n,n)) # using the elastic jacobian + # EE = IS + dp/R0 * (∂σ/∂ε)_e : ((3/2) ID - n ⊗ n) + EE = IS(T) + dp/R0 * dcontract(jacobian, 1.5*ID(T) - otimes(n,n)) # using the elastic jacobian ED = dcontract(inv(EE), jacobian) # J = ED - (ED : n) ⊗ (n : ED) / (n : ED : n) jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED)) / dcontract(dcontract(n, ED), n) @@ -87,6 +88,7 @@ function integrate_material!(material::IdealPlastic) cumeq=cumeq, jacobian=jacobian) material.variables_new = variables_new + return nothing end end diff --git a/src/increments.jl b/src/increments.jl index 3e7ac86..1dc40c4 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -50,14 +50,14 @@ If `max_iter` is reached and the error measure is still `tol` or greater, Note the timestep is **not** committed; we call `integrate_material!`, but not `update_material!`. Only `material.variables_new` is updated. """ -function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, +function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{T} where T <: Real, dt::Real, update_dstrain!::Function; max_iter::Integer=50, tol::Real=1e-9) converged = false stress0 = tovoigt(material.variables.stress) # observed for i=1:max_iter material.ddrivers.time = dt - material.ddrivers.strain = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) + material.ddrivers.strain = fromvoigt(Symm2{T}, dstrain; offdiagscale=2.0) integrate_material!(material) stress = tovoigt(material.variables_new.stress) # predicted dstress = stress - stress0 diff --git a/src/utilities.jl b/src/utilities.jl index cc51e64..24d3f5c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -20,8 +20,13 @@ const Symm4{T} = SymmetricTensor{4,3,T} Kronecker delta, defined by delta(i, j) = (i == j) ? 1 : 0. """ -delta(i::Integer, j::Integer) = (i == j) ? 1 : 0 +delta(i::T, j::T) where T <: Integer = (i == j) ? one(T) : zero(T) +# TODO: We could probably remove the type argument, and just let the results be +# inferred as Symm4{Int64}, Symm4{Rational{Int64}} and similar. Simpler to use, +# and those behave correctly in calculations with types involving other reals +# such as Float64. +# Performance implications? Is the Julia compiler smart enough to optimize? """ II(T::Type=Float64) From cbfa2dbe567da643a88a363ac012a80c909ed8d1 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 16:27:45 +0300 Subject: [PATCH 25/66] Remove outdated tests --- test/runtests.jl | 6 ------ test/test_abstract_material.jl | 15 --------------- 2 files changed, 21 deletions(-) delete mode 100644 test/test_abstract_material.jl diff --git a/test/runtests.jl b/test/runtests.jl index 0913188..7cbd811 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,9 +4,6 @@ using Materials, Test @testset "Test Materials.jl" begin - # @testset "test abstract material" begin - # include("test_abstract_material.jl") - # end @testset "test ideal plastic material model" begin include("test_idealplastic.jl") end @@ -25,7 +22,4 @@ using Materials, Test @testset "test biaxial increment" begin include("test_biaxial_increment.jl") end - # @testset "test viscoplastic material model" begin - # include("test_viscoplastic.jl") - # end end diff --git a/test/test_abstract_material.jl b/test/test_abstract_material.jl deleted file mode 100644 index ba1af89..0000000 --- a/test/test_abstract_material.jl +++ /dev/null @@ -1,15 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -using Test - -struct MyMaterial <: AbstractMaterial - E :: Float64 -end - -function MyMaterial() - error("MyMaterial needs at least `E` defined.") -end - -mat = Material(MyMaterial, (E=210.0,)) -@test mat.properties.E == 210.0 From 4a3dbbe1f8ada5e1690ebdb559a59c0a22324c5c Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 16:28:25 +0300 Subject: [PATCH 26/66] Run the stress-driven uniaxial increment test, too --- test/runtests.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 7cbd811..03c839c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,4 +22,7 @@ using Materials, Test @testset "test biaxial increment" begin include("test_biaxial_increment.jl") end + @testset "test stress-driven uniaxial increment" begin + include("test_stress_driven_uniaxial_increment.jl") + end end From 96092e5eb4d92ec1753c1d0325ed48c890e7ff8d Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 17:32:35 +0300 Subject: [PATCH 27/66] Clean up tests --- test/test_biaxial_increment.jl | 24 +++---- test/test_chaboche.jl | 64 ++++--------------- test/test_chaboche_shear.jl | 60 ++++++----------- test/test_idealplastic.jl | 49 +++----------- test/test_idealplastic_shear.jl | 22 +++---- test/test_stress_driven_uniaxial_increment.jl | 32 +++++----- test/test_uniaxial_increment.jl | 20 ++---- 7 files changed, 87 insertions(+), 184 deletions(-) diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index 734c6c8..e191989 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -1,18 +1,20 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE + using Test, Tensors + dtime = 0.25 -parameters = ChabocheParameterState(E = 200.0e3, - nu = 0.3, - R0 = 100.0, - Kn = 100.0, - nn = 10.0, - C1 = 10000.0, - D1 = 100.0, - C2 = 50000.0, - D2 = 1000.0, - Q = 50.0, - b = 0.1) +parameters = ChabocheParameterState(E=200.0e3, + nu=0.3, + R0=100.0, + Kn=100.0, + nn=10.0, + C1=10000.0, + D1=100.0, + C2=50000.0, + D2=1000.0, + Q=50.0, + b=0.1) mat = Chaboche(parameters = parameters) times = [mat.drivers.time] stresses = [copy(tovoigt(mat.variables.stress))] diff --git a/test/test_chaboche.jl b/test/test_chaboche.jl index c166cc3..8607928 100644 --- a/test/test_chaboche.jl +++ b/test/test_chaboche.jl @@ -22,63 +22,25 @@ e33_ = data[:,13] strains = [[e11_[i], e22_[i], e33_[i], e23_[i], e13_[i], e12_[i]] for i in 1:length(ts)] -parameters = ChabocheParameterState(E = 200.0e3, - nu = 0.3, - R0 = 100.0, - Kn = 100.0, - nn = 10.0, - C1 = 10000.0, - D1 = 100.0, - C2 = 50000.0, - D2 = 1000.0, - Q = 50.0, - b = 0.1) +parameters = ChabocheParameterState(E=200.0e3, + nu=0.3, + R0=100.0, + Kn=100.0, + nn=10.0, + C1=10000.0, + D1=100.0, + C2=50000.0, + D2=1000.0, + Q=50.0, + b=0.1) chabmat = Chaboche(parameters = parameters) s33s = [chabmat.variables.stress[3,3]] for i=2:length(ts) - dtime = ts[i]-ts[i-1] - dstrain = fromvoigt(Symm2{Float64}, strains[i]-strains[i-1]; offdiagscale=2.0) + dtime = ts[i] - ts[i-1] + dstrain = fromvoigt(Symm2{Float64}, strains[i] - strains[i-1]; offdiagscale=2.0) chabmat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) integrate_material!(chabmat) update_material!(chabmat) push!(s33s, chabmat.variables.stress[3,3]) end @test isapprox(s33s, s33_; rtol=0.05) - -# function test_chab() -# mat = Material(Chaboche, ()) -# mat.properties.youngs_modulus = 200.0e3 -# mat.properties.poissons_ratio = 0.3 -# mat.properties.yield_stress = 100.0 -# mat.properties.K_n = 100.0 -# mat.properties.n_n = 10.0 -# mat.properties.C_1 = 10000.0 -# mat.properties.D_1 = 100.0 -# mat.properties.C_2 = 50000.0 -# mat.properties.D_2 = 1000.0 -# mat.properties.Q = 50.0 -# mat.properties.b = 0.1 -# -# mat.stress = zeros(6) -# s33s = [0.0] -# for i=2:length(ts) -# dtime = ts[i]-ts[i-1] -# dstrain = strains[i]-strains[i-1] -# mat.dtime = dtime -# mat.dstrain = dstrain -# integrate_material!(mat) -# mat.time += mat.dtime -# mat.strain .+= mat.dstrain -# mat.stress .+= mat.dstress -# mat.properties.plastic_strain .+= mat.properties.dplastic_strain -# mat.properties.backstress1 .+= mat.properties.dbackstress1 -# mat.properties.backstress2 .+= mat.properties.dbackstress2 -# mat.properties.R += mat.properties.dR -# mat.properties.cumulative_equivalent_plastic_strain += mat.properties.dcumulative_equivalent_plastic_strain -# push!(s33s, copy(mat.stress[3])) -# end -# # @test isapprox(s33s, s33_; rtol=0.001) -# end - -# using BenchmarkTools -# @btime test_chab() diff --git a/test/test_chaboche_shear.jl b/test/test_chaboche_shear.jl index b9c4c94..09340f7 100644 --- a/test/test_chaboche_shear.jl +++ b/test/test_chaboche_shear.jl @@ -3,32 +3,19 @@ using Test, Tensors -# mat = Material(Chaboche, tuple()) -# props = mat.properties -# props.youngs_modulus = 200.0e3 -# props.poissons_ratio = 0.3 -# props.yield_stress = 100.0 -# props.K_n = 100.0 -# props.n_n = 3.0 -# props.C_1 = 0.0 -# props.D_1 = 100.0 -# props.C_2 = 0.0 -# props.D_2 = 1000.0 -# props.Q = 0.0 -# props.b = 0.1 E = 200.0e3 nu = 0.3 -parameters = ChabocheParameterState(E = E, - nu = nu, - R0 = 100.0, - Kn = 100.0, - nn = 3.0, - C1 = 0.0, - D1 = 100.0, - C2 = 0.0, - D2 = 1000.0, - Q = 0.0, - b = 0.1) +parameters = ChabocheParameterState(E=E, + nu=nu, + R0=100.0, + Kn=100.0, + nn=3.0, + C1=0.0, + D1=100.0, + C2=0.0, + D2=1000.0, + Q=0.0, + b=0.1) mat = Chaboche(parameters = parameters) times = [0.0] loads = [0.0] @@ -38,38 +25,29 @@ syield = 100.0 # vm = sqrt(3)*G*ga | ea = ga ea = 2*syield/(sqrt(3)*G) # Go to elastic border -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] + ea*dt) # Proceed to plastic flow -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] + ea*dt) # Reverse direction -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] - ea*dt) # Continue and pass yield criterion -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] - ea*dt) -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] - ea*dt) eeqs = [mat.variables.cumeq] stresses = [copy(tovoigt(mat.variables.stress))] for i=2:length(times) - dtime = times[i]-times[i-1] - dstrain31 = loads[i]-loads[i-1] + dtime = times[i] - times[i-1] + dstrain31 = loads[i] - loads[i-1] dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] - # mat.dtime = dtime - # mat.dstrain = dstrain dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - mat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain_) + mat.ddrivers = ChabocheDriverState(time=dtime, strain=dstrain_) integrate_material!(mat) - # mat.time += mat.dtime - # mat.strain .+= mat.dstrain - # mat.stress .+= mat.dstress - # mat.properties.plastic_strain .+= mat.properties.dplastic_strain - # mat.properties.backstress1 .+= mat.properties.dbackstress1 - # mat.properties.backstress2 .+= mat.properties.dbackstress2 - # mat.properties.R += mat.properties.dR update_material!(mat) push!(stresses, copy(tovoigt(mat.variables.stress))) push!(eeqs, mat.variables.cumeq) diff --git a/test/test_idealplastic.jl b/test/test_idealplastic.jl index bff2df6..f1b3746 100644 --- a/test/test_idealplastic.jl +++ b/test/test_idealplastic.jl @@ -2,75 +2,44 @@ # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE using Test, Tensors -# time = [...] -# strain = [...] -# stress = run_simulation(mat, time, strain) -# @test isapprox(stress, stress_expected) -# -# mat = Material(IdealPlastic, tuple()) -# mat.properties.youngs_modulus = 200.0e3 -# mat.properties.poissons_ratio = 0.3 -# mat.properties.yield_stress = 100.0 +parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, + poissons_ratio=0.3, + yield_stress=100.0) -parameters = IdealPlasticParameterState(youngs_modulus = 200.0e3, - poissons_ratio = 0.3, - yield_stress = 100.0) - -dstrain_dtime = fromvoigt(Symm2{Float64},1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) -ddrivers = IdealPlasticDriverState(time = 0.25, strain = 0.25*dstrain_dtime) +dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) +ddrivers = IdealPlasticDriverState(time=0.25, strain=0.25*dstrain_dtime) mat = IdealPlastic(parameters=parameters, ddrivers=ddrivers) -# mat.dtime = 0.25 -# mat.dstrain .= 1.0e-3*[-0.3, -0.3, 1.0, 0.0, 0.0, 0.0]*mat.dtime integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, fromvoigt(Symm2, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0])) -# mat.time += mat.dtime -# mat.strain .+= mat.dstrain -# mat.stress .+= mat.dstress mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, fromvoigt(Symm2, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) @test isapprox(mat.variables.cumeq, 0.0; atol=1.0e-12) -# mat.time += mat.dtime -# mat.strain .+= mat.dstrain -# mat.stress .+= mat.dstress -# mat.dstrain[:] .= 1.0e-3*[-0.5, -0.5, 1.0, 0.0, 0.0, 0.0]*mat.dtime dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0) -ddrivers = IdealPlasticDriverState(time = 0.25, strain = 0.25*dstrain_dtime) +ddrivers = IdealPlasticDriverState(time=0.25, strain=0.25*dstrain_dtime) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, fromvoigt(Symm2, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) @test isapprox(mat.variables.cumeq, 0.25*1.0e-3) -# mat.time += mat.dtime -# mat.strain .+= mat.dstrain -# mat.stress .+= mat.dstress -# mat.dstrain[:] .= -1.0e-3*[-0.3, -0.3, 1.0, 0.0, 0.0, 0.0]*mat.dtime dstrain_dtime = fromvoigt(Symm2{Float64}, -1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) -ddrivers = IdealPlasticDriverState(time = 0.25, strain = 0.25*dstrain_dtime) +ddrivers = IdealPlasticDriverState(time=0.25, strain=0.25*dstrain_dtime) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, fromvoigt(Symm2, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) -# mat.time += mat.dtime -# mat.strain .+= mat.dstrain -# mat.stress .+= mat.dstress -# mat.dtime = 1.0 -# m1 = 1.0e-3*[-0.3, -0.3, 1.0, 0.0, 0.0, 0.0] -# m2 = 1.0e-3*[-0.5, -0.5, 1.0, 0.0, 0.0, 0.0] -# mat.dstrain[:] .= -m1*0.75 - m2*0.25 dstrain_dtime = (-0.75*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) - -0.25*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0)) -ddrivers = IdealPlasticDriverState(time = 1.0, strain = dstrain_dtime) + -0.25*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0)) +ddrivers = IdealPlasticDriverState(time=1.0, strain=dstrain_dtime) mat.ddrivers = ddrivers integrate_material!(mat) integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, fromvoigt(Symm2, [-100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) -# @test isapprox(mat.properties.dplastic_multiplier, 0.25*1.0e-3) diff --git a/test/test_idealplastic_shear.jl b/test/test_idealplastic_shear.jl index abf8a29..108cee5 100644 --- a/test/test_idealplastic_shear.jl +++ b/test/test_idealplastic_shear.jl @@ -6,9 +6,9 @@ using Test, Tensors E = 200.0e3 nu = 0.3 syield = 100.0 -parameters = IdealPlasticParameterState(youngs_modulus = E, - poissons_ratio = nu, - yield_stress = syield) +parameters = IdealPlasticParameterState(youngs_modulus=E, + poissons_ratio=nu, + yield_stress=syield) mat = IdealPlastic(parameters=parameters) @@ -19,26 +19,24 @@ G = 0.5*E/(1+nu) # vm = sqrt(3)*G*ga | ea = ga ea = 2*syield/(sqrt(3)*G) # Go to elastic border -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] + ea*dt) # Proceed to plastic flow -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] + ea*dt) # Reverse direction -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] - ea*dt) # Continue and pass yield criterion -push!(times, times[end]+dt) +push!(times, times[end] + dt) push!(loads, loads[end] - 2*ea*dt) stresses = [copy(tovoigt(mat.variables.stress))] for i=2:length(times) - dtime = times[i]-times[i-1] - dstrain31 = loads[i]-loads[i-1] + dtime = times[i] - times[i-1] + dstrain31 = loads[i] - loads[i-1] dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - ddrivers = IdealPlasticDriverState(time = dtime, strain = dstrain_) - #mat.dtime = dtime - #mat.dstrain = dstrain + ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) diff --git a/test/test_stress_driven_uniaxial_increment.jl b/test/test_stress_driven_uniaxial_increment.jl index a0d359d..1ad71ee 100644 --- a/test/test_stress_driven_uniaxial_increment.jl +++ b/test/test_stress_driven_uniaxial_increment.jl @@ -2,18 +2,18 @@ # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE using Test, Tensors, Materials -parameters = ChabocheParameterState(E = 200.0e3, - nu = 0.3, - R0 = 100.0, - Kn = 100.0, - nn = 10.0, - C1 = 10000.0, - D1 = 100.0, - C2 = 50000.0, - D2 = 1000.0, - Q = 0.0, - b = 0.1) -material = Chaboche(parameters = parameters) +parameters = ChabocheParameterState(E=200.0e3, + nu=0.3, + R0=100.0, + Kn=100.0, + nn=10.0, + C1=10000.0, + D1=100.0, + C2=50000.0, + D2=1000.0, + Q=0.0, + b=0.1) +material = Chaboche(parameters=parameters) dtime = 0.25 times = [material.drivers.time] @@ -27,10 +27,10 @@ stresses_expected = [[ 50.0, 0.0, 0.0, 0.0, 0.0, 0.0], [ 150.0, 0.0, 0.0, 0.0, 0.0, 0.0], [ 100.0, 0.0, 0.0, 0.0, 0.0, 0.0], [ -100.0, 0.0, 0.0, 0.0, 0.0, 0.0]] -dstress = 50.0 +dstress = 50.0 -dtimes = [dtime, dtime, dtime, 1e3, dtime, 1e3] -dstresses11 = [dstress, dstress, dstress, 0.0, -dstress, -4*dstress] +dtimes = [dtime, dtime, dtime, 1e3, dtime, 1e3] +dstresses11 = [dstress, dstress, dstress, 0.0, -dstress, -4*dstress] for i in 1:length(dtimes) dstress11 = dstresses11[i] @@ -49,4 +49,4 @@ dstrain_creep = strains[5] - strains[4] @test isapprox(dstrain_creep[3], -dstrain_creep[1]*0.5; atol=1e-4) dcumeq = cumeqs[end] - cumeqs[end-1] -@test dcumeq > 0 \ No newline at end of file +@test dcumeq > 0 diff --git a/test/test_uniaxial_increment.jl b/test/test_uniaxial_increment.jl index 42aaef6..972bb40 100644 --- a/test/test_uniaxial_increment.jl +++ b/test/test_uniaxial_increment.jl @@ -1,17 +1,15 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE + using Test, Tensors + dtime = 0.25 -# mat = Material(IdealPlastic, ()) -# mat.properties.youngs_modulus = 200.0e3 -# mat.properties.poissons_ratio = 0.3 -# mat.properties.yield_stress = 100.0 -parameters = IdealPlasticParameterState(youngs_modulus = 200.0e3, - poissons_ratio = 0.3, - yield_stress = 100.0) +parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, + poissons_ratio=0.3, + yield_stress=100.0) mat = IdealPlastic(parameters=parameters) -times = [mat.drivers.time] -stresses = [copy(tovoigt(mat.variables.stress))] +# times = [mat.drivers.time] +# stresses = [copy(tovoigt(mat.variables.stress))] stresses_expected = [[50.0, 0.0, 0.0, 0.0, 0.0, 0.0], [100.0, 0.0, 0.0, 0.0, 0.0, 0.0], [100.0, 0.0, 0.0, 0.0, 0.0, 0.0], @@ -29,10 +27,6 @@ for i in 1:length(dtimes) dstrain11 = dstrains11[i] dtime = dtimes[i] uniaxial_increment!(mat, dstrain11, dtime) - # uniaxial_increment!(mat, dstrain11, dtime; dstrain = copy(tovoigt(mat.ddrivers.strain))*dstrain11/mat.ddrivers.strain[1,1]*dtime/mat.ddrivers.time) - # mat.time += mat.dtime - # mat.strain .+= mat.dstrain - # mat.stress .+= mat.dstress update_material!(mat) # push!(times, mat.drivers.time) # push!(stresses, copy(tovoigt(mat.variables.stress))) From a563b45bdc51bce685e2d8662cd97111bab16591 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 17:32:42 +0300 Subject: [PATCH 28/66] Add unit test for Utilities module --- test/runtests.jl | 3 ++ test/test_utilities.jl | 71 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 test/test_utilities.jl diff --git a/test/runtests.jl b/test/runtests.jl index 03c839c..d696e1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,9 @@ using Materials, Test @testset "Test Materials.jl" begin + @testset "test utilities" begin + include("test_utilities.jl") + end @testset "test ideal plastic material model" begin include("test_idealplastic.jl") end diff --git a/test/test_utilities.jl b/test/test_utilities.jl new file mode 100644 index 0000000..868283e --- /dev/null +++ b/test/test_utilities.jl @@ -0,0 +1,71 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE + +using Test, Tensors, LinearAlgebra + +@test delta(1, 2) isa Int64 +@test delta(BigInt(1), BigInt(2)) isa BigInt +@test_throws MethodError delta(1.0, 2.0) + +@test isapprox(tovoigt(II()), I(6)) +@test let Z = zeros(3, 3) + isapprox(tovoigt(IT()), Array([I(3) Z; + Z Z])) + end + +@test let Z = zeros(3, 3) + isapprox(tovoigt(IS()), Array([I(3) Z; + Z 1//2*I(3)])) +end +@test let Z = zeros(3, 3) + isapprox(tovoigt(IA()), Array([Z Z; + Z 1//2*I(3)])) +end + +@test let Z = zeros(3, 3), + O = ones(3, 3) + isapprox(tovoigt(IV()), Array([1//3*O Z; + Z Z])) +end +@test let Z = zeros(3, 3) + isapprox(tovoigt(ID()), Array([ 2//3 -1//3 -1//3 0 0 0; + -1//3 2//3 -1//3 0 0 0; + -1//3 -1//3 2//3 0 0 0; + 0 0 0 1//2 0 0; + 0 0 0 0 1//2 0; + 0 0 0 0 0 1//2])) +end + +@test isapprox(tovoigt(isotropic_elasticity_tensor(10.0, 0.3)), [10.6 10.0 10.0 0.0 0.0 0.0; + 10.0 10.6 10.0 0.0 0.0 0.0; + 10.0 10.0 10.6 0.0 0.0 0.0; + 0.0 0.0 0.0 0.3 0.0 0.0; + 0.0 0.0 0.0 0.0 0.3 0.0; + 0.0 0.0 0.0 0.0 0.0 0.3]) + +@test all(isapprox(a, b) for (a, b) in zip(lame(1e11, 0.3), (5.769230769230769e10, 3.846153846153846e10))) +@test all(isapprox(a, b) for (a, b) in zip(delame(lame(1e11, 0.3)...), (1e11, 0.3))) + +function test_debang() # just to introduce a local scope so the names `f!`, `f` and `out` are local to this test + function f!(out, x) + out[:] = [sin(elt) for elt in x] + return nothing + end + out = [0.0] + @test all([f!(out, [pi/4]) == nothing, + isapprox(out[1], 1/sqrt(2))]) + + f = debang(f!) + @test f isa Function + out = [0.0] + @test all([isapprox(f([pi/4])[1], 1/sqrt(2)), + out[1] == 0.0]) +end +test_debang() + +# The output of g must be an AbstractArray to use ForwardDiff.jacobian (the default `dfdx`) in find_root. +let g(x) = [(1 - x[1]^2) + x[2]], + x0 = [0.8, 0.2] + @test !isapprox(g(x0), [0.0], atol=1e-15) # find_root should have to actually do something + @test isapprox(g(find_root(g, x0)), [0.0], atol=1e-15) +end From 84ecb806181a5d533553ea4b4521458c2f84b747 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Tue, 9 Jun 2020 17:37:26 +0300 Subject: [PATCH 29/66] Slightly clean up debang test --- test/test_utilities.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 868283e..507b165 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -53,13 +53,13 @@ function test_debang() # just to introduce a local scope so the names `f!`, `f` end out = [0.0] @test all([f!(out, [pi/4]) == nothing, - isapprox(out[1], 1/sqrt(2))]) + isapprox(out, [1/sqrt(2)])]) f = debang(f!) @test f isa Function out = [0.0] - @test all([isapprox(f([pi/4])[1], 1/sqrt(2)), - out[1] == 0.0]) + @test all([isapprox(f([pi/4]), [1/sqrt(2)]), + out == [0.0]]) end test_debang() From 80dd466d428be33e233f03d678eb78b095d29fc3 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 09:44:39 +0300 Subject: [PATCH 30/66] Lint.jl is dead, need to look for alternatives. Deleted the obsolete `test/run_lint.jl` script for now. --- test/run_lint.jl | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 test/run_lint.jl diff --git a/test/run_lint.jl b/test/run_lint.jl deleted file mode 100644 index 6e7f672..0000000 --- a/test/run_lint.jl +++ /dev/null @@ -1,17 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE - -using Lint -using Base.Test - -results = lintpkg("Materials") -if !isempty(results) - info("Lint.jl is a tool that uses static analysis to assist in the development process by detecting common bugs and potential issues.") - info("For this package, Lint.jl report is following:") - display(results) - info("For more information, see https://lintjl.readthedocs.io/en/stable/") - warn("Package syntax test has failed.") - @test isempty(results) -else - info("Lint.jl: syntax check pass.") -end From d5ee9ecca0a7363181329280d0d7924f6d8f3de5 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 11:31:09 +0300 Subject: [PATCH 31/66] Clean up utility tests --- test/test_utilities.jl | 72 ++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 507b165..36dfb69 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -3,50 +3,47 @@ using Test, Tensors, LinearAlgebra -@test delta(1, 2) isa Int64 +# Kronecker delta +@test delta(1, 1) == 1 +@test delta(1, 2) == 0 +@test_throws MethodError delta(1.0, 2.0) # indices must be integers +@test_throws MethodError delta(1, BigInt(2)) # both must have the same type +@test delta(1, 2) isa Int # the output type matches the input @test delta(BigInt(1), BigInt(2)) isa BigInt -@test_throws MethodError delta(1.0, 2.0) -@test isapprox(tovoigt(II()), I(6)) -@test let Z = zeros(3, 3) - isapprox(tovoigt(IT()), Array([I(3) Z; - Z Z])) - end +# Various tensors +let Z3 = zeros(3, 3), + O3 = ones(3, 3), + I3 = I(3) + @test isapprox(tovoigt(II()), I(6)) + @test isapprox(tovoigt(IT()), [I3 Z3; + Z3 Z3]) -@test let Z = zeros(3, 3) - isapprox(tovoigt(IS()), Array([I(3) Z; - Z 1//2*I(3)])) -end -@test let Z = zeros(3, 3) - isapprox(tovoigt(IA()), Array([Z Z; - Z 1//2*I(3)])) -end + @test isapprox(tovoigt(IS()), [I3 Z3; + Z3 1//2*I3]) + @test isapprox(tovoigt(IA()), [Z3 Z3; + Z3 1//2*I3]) -@test let Z = zeros(3, 3), - O = ones(3, 3) - isapprox(tovoigt(IV()), Array([1//3*O Z; - Z Z])) -end -@test let Z = zeros(3, 3) - isapprox(tovoigt(ID()), Array([ 2//3 -1//3 -1//3 0 0 0; - -1//3 2//3 -1//3 0 0 0; - -1//3 -1//3 2//3 0 0 0; - 0 0 0 1//2 0 0; - 0 0 0 0 1//2 0; - 0 0 0 0 0 1//2])) -end + @test isapprox(tovoigt(IV()), [1//3*O3 Z3; + Z3 Z3]) + @test isapprox(tovoigt(ID()), [(I3 - 1//3*O3) Z3; + Z3 1//2*I3]) -@test isapprox(tovoigt(isotropic_elasticity_tensor(10.0, 0.3)), [10.6 10.0 10.0 0.0 0.0 0.0; - 10.0 10.6 10.0 0.0 0.0 0.0; - 10.0 10.0 10.6 0.0 0.0 0.0; - 0.0 0.0 0.0 0.3 0.0 0.0; - 0.0 0.0 0.0 0.0 0.3 0.0; - 0.0 0.0 0.0 0.0 0.0 0.3]) + @test let lambda = 10.0, + mu = 1.0 + isapprox(tovoigt(isotropic_elasticity_tensor(lambda, mu)), [(lambda*O3 + 2*mu*I3) Z3; + Z3 mu*I3]) + end +end -@test all(isapprox(a, b) for (a, b) in zip(lame(1e11, 0.3), (5.769230769230769e10, 3.846153846153846e10))) -@test all(isapprox(a, b) for (a, b) in zip(delame(lame(1e11, 0.3)...), (1e11, 0.3))) +# Lamé parameters for isotropic solids +@test all(isapprox(result, expected) + for (result, expected) in zip(lame(1e11, 0.3), (5.769230769230769e10, 3.846153846153846e10))) +@test all(isapprox(result, expected) + for (result, expected) in zip(delame(lame(1e11, 0.3)...), (1e11, 0.3))) -function test_debang() # just to introduce a local scope so the names `f!`, `f` and `out` are local to this test +# Mutating function to non-mutating function conversion +function test_debang() # introduce a local scope so the names `f!`, `f` and `out` are local to this test. function f!(out, x) out[:] = [sin(elt) for elt in x] return nothing @@ -63,6 +60,7 @@ function test_debang() # just to introduce a local scope so the names `f!`, `f` end test_debang() +# Newton root finder # The output of g must be an AbstractArray to use ForwardDiff.jacobian (the default `dfdx`) in find_root. let g(x) = [(1 - x[1]^2) + x[2]], x0 = [0.8, 0.2] From eb38285ff65606f97318bfb658d5cbfa27196976 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 11:32:50 +0300 Subject: [PATCH 32/66] Ehm, that comment should be in the function's docstring instead. --- src/utilities.jl | 2 +- test/test_utilities.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 24d3f5c..fa1a22c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -159,7 +159,7 @@ end The input `x` is the initial guess. The default `dfdx=nothing` uses `ForwardDiff.jacobian` to compute the jacobian -automatically. +automatically. In this case the output of `f` must be an `AbstractArray`. `tol` is measured in the vector norm of the change in `x` between successive iterations. diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 36dfb69..836665b 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -61,7 +61,6 @@ end test_debang() # Newton root finder -# The output of g must be an AbstractArray to use ForwardDiff.jacobian (the default `dfdx`) in find_root. let g(x) = [(1 - x[1]^2) + x[2]], x0 = [0.8, 0.2] @test !isapprox(g(x0), [0.0], atol=1e-15) # find_root should have to actually do something From 2f54800dc50aa24d1db9b826d361d84487753668 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:25:05 +0300 Subject: [PATCH 33/66] Comment in Chaboche that the viscoplastic model is a special case. --- src/chaboche.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chaboche.jl b/src/chaboche.jl index e049de8..a53d069 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -19,6 +19,8 @@ end # TODO: complete this docstring """Parameter state for Chaboche material. +The classical viscoplastic material is a special case of this model with `C1 = C2 = 0`. + `E`: Young's modulus `nu`: Poisson's ratio `R0`: initial yield strength From 3825267b48dbc70fa246e625841e77283c5d9491 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:25:20 +0300 Subject: [PATCH 34/66] Oops, fix Chaboche implementation. --- src/chaboche.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index a53d069..9cd9a67 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -80,7 +80,7 @@ end Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. """ function state_to_vector(sigma::U, R::T, X1::U, X2::U) where U <: Symm2{T} where T <: Real - return [tovoigt(sigma), R, tovoigt(X1), tovoigt(X2)] + return [tovoigt(sigma); R; tovoigt(X1); tovoigt(X2)]::Vector{T} end """ @@ -89,10 +89,10 @@ end Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. """ function state_from_vector(x::AbstractVector{T}) where T <: Real - sigma = fromvoigt(Symm2{T}, @view x[1:6]) - R = x[7] - X1 = fromvoigt(Symm2{T}, @view x[8:13]) - X2 = fromvoigt(Symm2{T}, @view x[14:19]) + sigma::Symm2{T} = fromvoigt(Symm2{T}, @view x[1:6]) + R::T = x[7] + X1::Symm2{T} = fromvoigt(Symm2{T}, @view x[8:13]) + X2::Symm2{T} = fromvoigt(Symm2{T}, @view x[14:19]) return sigma, R, X1, X2 end @@ -233,14 +233,15 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: # Δ(...) = (...)_new - (...)_old # # Then move the delta terms to the RHS to get the standard form, (stuff) = 0. + # Also, below we avoid the multiplication and division that cancel each other + # in the last terms of the equations for ΔX1 and ΔX2. # dstrain_plastic = dp*n dstrain_elastic = dstrain - dstrain_plastic tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain_elastic)) F[7] = R - R_ + b*(Q - R_)*dp - # dp is a scalar, so it commutes in multiplication. This allows us to avoid the division by C1. - tovoigt!(view(F, 8:13), X1 + (-1.0 + dp*(2.0/3.0*C1*n - D1*X1_))) - tovoigt!(view(F, 14:19), X2 + (-1.0 + dp*(2.0/3.0*C2*n - D2*X2_))) + tovoigt!(view(F, 8:13), X1 - X1_ + dp*(2.0/3.0*C1*n - D1*X1_)) + tovoigt!(view(F, 14:19), X2 - X2_ + dp*(2.0/3.0*C2*n - D2*X2_)) end return g! end From de0921aefb71702ebfb4c2e5bdb507f5712e221b Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:25:34 +0300 Subject: [PATCH 35/66] Oops, add missing imports. --- src/increments.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/increments.jl b/src/increments.jl index 1dc40c4..bde08d5 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -3,9 +3,10 @@ module Increments +import LinearAlgebra: norm import Tensors: tovoigt, fromvoigt -import ..AbstractMaterial +import ..AbstractMaterial, ..integrate_material! import ..Utilities: Symm2 export optimize_dstrain!, uniaxial_increment!, biaxial_increment!, stress_driven_uniaxial_increment! From c5d4e691929efaec5d918de9b09bdc4e27080f38 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:26:07 +0300 Subject: [PATCH 36/66] Work around "T not defined" Either I've missed how exactly to say what I meant, or Julia's static type system doesn't talk with the runtime as well as I'd hoped. --- src/increments.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/increments.jl b/src/increments.jl index bde08d5..d5856e2 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -51,11 +51,12 @@ If `max_iter` is reached and the error measure is still `tol` or greater, Note the timestep is **not** committed; we call `integrate_material!`, but not `update_material!`. Only `material.variables_new` is updated. """ -function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{T} where T <: Real, +function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, dt::Real, update_dstrain!::Function; max_iter::Integer=50, tol::Real=1e-9) converged = false stress0 = tovoigt(material.variables.stress) # observed + T = typeof(dstrain[1]) for i=1:max_iter material.ddrivers.time = dt material.ddrivers.strain = fromvoigt(Symm2{T}, dstrain; offdiagscale=2.0) From 32999c624fb84923131804f0d04ed9129f6f2ca1 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:27:44 +0300 Subject: [PATCH 37/66] Prettify loop --- src/increments.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/increments.jl b/src/increments.jl index d5856e2..f4c0c3b 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -54,7 +54,6 @@ Note the timestep is **not** committed; we call `integrate_material!`, but not function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, dt::Real, update_dstrain!::Function; max_iter::Integer=50, tol::Real=1e-9) - converged = false stress0 = tovoigt(material.variables.stress) # observed T = typeof(dstrain[1]) for i=1:max_iter @@ -66,12 +65,10 @@ function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{< jacobian = tovoigt(material.variables_new.jacobian) e = update_dstrain!(dstrain, dstress, jacobian) if e < tol - converged = true - break + return nothing end end - converged || error("No convergence in strain increment") - return nothing + error("No convergence in strain increment") end """ From 4a1048b5bca7b24f2d7d9dd185b0e7208157abbc Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:27:57 +0300 Subject: [PATCH 38/66] Oops, the jacobian is obviously a matrix --- src/increments.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/increments.jl b/src/increments.jl index f4c0c3b..0a8bbb3 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -25,8 +25,8 @@ The `dstrain` supplied to this routine is the initial guess for the optimization. At each iteration, it must be updated by the user-defined corrector `update_dstrain!`, whose call signature is expected to be: - update_dstrain!(dstrain::V, dstress::V, jacobian::V) - where V <: AbstractVector{<:Real} + update_dstrain!(dstrain::V, dstress::V, jacobian::AbstractArray{T}) + where V <: AbstractVector{T} where T <: Real -> err::Real `dstrain` is the current value of the strain increment, in Voigt format. @@ -88,7 +88,7 @@ See `optimize_dstrain!`. function uniaxial_increment!(material::AbstractMaterial, dstrain11::Real, dt::Real; dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], max_iter::Integer=50, norm_acc::Real=1e-9) - function update_dstrain!(dstrain::V, dstress::V, jacobian::V) where V <: AbstractVector{<:Real} + function update_dstrain!(dstrain::V, dstress::V, jacobian::AbstractArray{T}) where V <: AbstractVector{T} where T <: Real dstr = -jacobian[2:end,2:end] \ dstress[2:end] dstrain[2:end] .+= dstr return norm(dstr) @@ -114,7 +114,7 @@ See `optimize_dstrain!`. function biaxial_increment!(material::AbstractMaterial, dstrain11::Real, dstrain12::Real, dt::Real; dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], max_iter::Integer=50, norm_acc::Real=1e-9) - function update_dstrain!(dstrain::V, dstress::V, jacobian::V) where V <: AbstractVector{<:Real} + function update_dstrain!(dstrain::V, dstress::V, jacobian::AbstractArray{T}) where V <: AbstractVector{T} where T <: Real dstr = -jacobian[2:end-1,2:end-1] \ dstress[2:end-1] dstrain[2:end-1] .+= dstr return norm(dstr) @@ -139,7 +139,7 @@ See `optimize_dstrain!`. function stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11::Real, dt::Real; dstrain::AbstractVector{<:Real}=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], max_iter::Integer=50, norm_acc::Real=1e-9) - function update_dstrain!(dstrain::V, dstress::V, jacobian::V) where V <: AbstractVector{<:Real} + function update_dstrain!(dstrain::V, dstress::V, jacobian::AbstractArray{T}) where V <: AbstractVector{T} where T <: Real # Mutation of `dstress` doesn't matter, since `dstress` is freshly generated at each iteration. # The lexical closure property gives us access to `dstress11` in this scope. dstress[1] -= dstress11 From d81ec8e780398d07c5bfb720a1ff856b0ae2b951 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:28:13 +0300 Subject: [PATCH 39/66] Clean up testset naming --- test/runtests.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index d696e1b..32211fe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,20 +7,20 @@ using Materials, Test @testset "test utilities" begin include("test_utilities.jl") end - @testset "test ideal plastic material model" begin + @testset "test ideal plastic uniaxial stress" begin include("test_idealplastic.jl") end @testset "test ideal plastic pure shear" begin include("test_idealplastic_shear.jl") end - @testset "test uniaxial increment" begin - include("test_uniaxial_increment.jl") + @testset "test chaboche uniaxial stress" begin + include("test_chaboche.jl") end @testset "test chaboche pure shear" begin include("test_chaboche_shear.jl") end - @testset "test chaboche uniaxial stress" begin - include("test_chaboche.jl") + @testset "test uniaxial increment" begin + include("test_uniaxial_increment.jl") end @testset "test biaxial increment" begin include("test_biaxial_increment.jl") From 8eef65a26c827d291b1c713ae54df5b74edb2a3e Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:28:28 +0300 Subject: [PATCH 40/66] Clean up tests some more --- test/test_biaxial_increment.jl | 63 +++++----- test/test_chaboche.jl | 76 ++++++------ test/test_chaboche_shear.jl | 116 +++++++++--------- test/test_idealplastic.jl | 79 ++++++------ test/test_idealplastic_shear.jl | 87 ++++++------- test/test_stress_driven_uniaxial_increment.jl | 2 +- test/test_uniaxial_increment.jl | 60 ++++----- test/test_utilities.jl | 24 ++-- 8 files changed, 260 insertions(+), 247 deletions(-) diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index e191989..1b1392c 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -3,38 +3,37 @@ using Test, Tensors -dtime = 0.25 -parameters = ChabocheParameterState(E=200.0e3, - nu=0.3, - R0=100.0, - Kn=100.0, - nn=10.0, - C1=10000.0, - D1=100.0, - C2=50000.0, - D2=1000.0, - Q=50.0, - b=0.1) -mat = Chaboche(parameters = parameters) -times = [mat.drivers.time] -stresses = [copy(tovoigt(mat.variables.stress))] -dstrain11 = 1e-3*dtime -dstrain12 = 1e-3*dtime +let dtime = 0.25, + parameters = ChabocheParameterState(E=200.0e3, + nu=0.3, + R0=100.0, + Kn=100.0, + nn=10.0, + C1=10000.0, + D1=100.0, + C2=50000.0, + D2=1000.0, + Q=50.0, + b=0.1), + mat = Chaboche{Float64}(parameters = parameters), + dstrain11 = 1e-3*dtime, + dstrain12 = 1e-3*dtime, + dtimes = [dtime, dtime, dtime, dtime, 1.0], + dstrains11 = dstrain11*[1.0, 1.0, 1.0, -1.0, -4.0], + dstrains12 = dstrain12*[1.0, 1.0, 1.0, -1.0, -4.0] -dtimes = [dtime, dtime, dtime, dtime, 1.0] -dstrains11 = [dstrain11, dstrain11, dstrain11, -dstrain11, -4*dstrain11] -dstrains12 = [dstrain12, dstrain12, dstrain12, -dstrain12, -4*dstrain12] -plasticity_test = zeros(length(dstrains11)-1) -for i in 1:length(dtimes) - dstrain11 = dstrains11[i] - dtime = dtimes[i] - biaxial_increment!(mat, dstrain11, dstrain12, dtime) - update_material!(mat) - push!(stresses, copy(tovoigt(mat.variables.stress))) - if i > 1 - plasticity_test[i-1] = mat.variables.cumeq > 0.0 + plastic_flow_occurred = zeros(length(dstrains11) - 1) + for i in 1:length(dtimes) + dstrain11 = dstrains11[i] + dstrain12 = dstrains12[i] + dtime = dtimes[i] + biaxial_increment!(mat, dstrain11, dstrain12, dtime) + update_material!(mat) + if i > 1 + plastic_flow_occurred[i-1] = (mat.variables.cumeq > 0.0) + end + @test !iszero(mat.variables.stress[1,1]) && !iszero(mat.variables.stress[1,2]) + @test isapprox(tovoigt(mat.variables.stress; offdiagscale=2.0)[2:5],zeros(4); atol=1e-5) end - @test !iszero(tovoigt(mat.variables.stress)[1]) && !iszero(tovoigt(mat.variables.stress)[end]) - @test isapprox(tovoigt(mat.variables.stress; offdiagscale=2.0)[2:5],zeros(4); atol=1e-5) + @test any(x->x==1, plastic_flow_occurred) end -@test any(x->x==1,plasticity_test) diff --git a/test/test_chaboche.jl b/test/test_chaboche.jl index 8607928..28ad5a1 100644 --- a/test/test_chaboche.jl +++ b/test/test_chaboche.jl @@ -4,43 +4,43 @@ using Test, Tensors using DelimitedFiles -path = joinpath("test_chaboche", "unitelement_results.rpt") -data = readdlm(path, Float64; skipstart=4) -ts = data[:,1] -s11_ = data[:,2] -s12_ = data[:,3] -s13_ = data[:,4] -s22_ = data[:,5] -s23_ = data[:,6] -s33_ = data[:,7] -e11_ = data[:,8] -e12_ = data[:,9] -e13_ = data[:,10] -e22_ = data[:,11] -e23_ = data[:,12] -e33_ = data[:,13] +let path = joinpath("test_chaboche", "unitelement_results.rpt"), + data = readdlm(path, Float64; skipstart=4), + ts = data[:,1], + s11_ = data[:,2], + s12_ = data[:,3], + s13_ = data[:,4], + s22_ = data[:,5], + s23_ = data[:,6], + s33_ = data[:,7], + e11_ = data[:,8], + e12_ = data[:,9], + e13_ = data[:,10], + e22_ = data[:,11], + e23_ = data[:,12], + e33_ = data[:,13], + strains = [[e11_[i], e22_[i], e33_[i], e23_[i], e13_[i], e12_[i]] for i in 1:length(ts)], + parameters = ChabocheParameterState(E=200.0e3, + nu=0.3, + R0=100.0, + Kn=100.0, + nn=10.0, + C1=10000.0, + D1=100.0, + C2=50000.0, + D2=1000.0, + Q=50.0, + b=0.1), + mat = Chaboche{Float64}(parameters=parameters) -strains = [[e11_[i], e22_[i], e33_[i], e23_[i], e13_[i], e12_[i]] for i in 1:length(ts)] - -parameters = ChabocheParameterState(E=200.0e3, - nu=0.3, - R0=100.0, - Kn=100.0, - nn=10.0, - C1=10000.0, - D1=100.0, - C2=50000.0, - D2=1000.0, - Q=50.0, - b=0.1) -chabmat = Chaboche(parameters = parameters) -s33s = [chabmat.variables.stress[3,3]] -for i=2:length(ts) - dtime = ts[i] - ts[i-1] - dstrain = fromvoigt(Symm2{Float64}, strains[i] - strains[i-1]; offdiagscale=2.0) - chabmat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) - integrate_material!(chabmat) - update_material!(chabmat) - push!(s33s, chabmat.variables.stress[3,3]) + s33s = [mat.variables.stress[3,3]] + for i=2:length(ts) + dtime = ts[i] - ts[i-1] + dstrain = fromvoigt(Symm2{Float64}, strains[i] - strains[i-1]; offdiagscale=2.0) + mat.ddrivers = ChabocheDriverState(time = dtime, strain = dstrain) + integrate_material!(mat) + update_material!(mat) + push!(s33s, mat.variables.stress[3,3]) + end + @test isapprox(s33s, s33_; rtol=0.05) end -@test isapprox(s33s, s33_; rtol=0.05) diff --git a/test/test_chaboche_shear.jl b/test/test_chaboche_shear.jl index 09340f7..10d5430 100644 --- a/test/test_chaboche_shear.jl +++ b/test/test_chaboche_shear.jl @@ -3,63 +3,65 @@ using Test, Tensors -E = 200.0e3 -nu = 0.3 -parameters = ChabocheParameterState(E=E, - nu=nu, - R0=100.0, - Kn=100.0, - nn=3.0, - C1=0.0, - D1=100.0, - C2=0.0, - D2=1000.0, - Q=0.0, - b=0.1) -mat = Chaboche(parameters = parameters) -times = [0.0] -loads = [0.0] -dt = 0.5 -G = 0.5*E/(1+nu) -syield = 100.0 -# vm = sqrt(3)*G*ga | ea = ga -ea = 2*syield/(sqrt(3)*G) -# Go to elastic border -push!(times, times[end] + dt) -push!(loads, loads[end] + ea*dt) - # Proceed to plastic flow -push!(times, times[end] + dt) -push!(loads, loads[end] + ea*dt) - # Reverse direction -push!(times, times[end] + dt) -push!(loads, loads[end] - ea*dt) - # Continue and pass yield criterion -push!(times, times[end] + dt) -push!(loads, loads[end] - ea*dt) -push!(times, times[end] + dt) -push!(loads, loads[end] - ea*dt) +let E = 200.0e3, + nu = 0.3, + parameters = ChabocheParameterState(E=E, + nu=nu, + R0=100.0, + Kn=100.0, + nn=3.0, + C1=0.0, + D1=100.0, + C2=0.0, + D2=1000.0, + Q=0.0, + b=0.1), + mat = Chaboche{Float64}(parameters=parameters), + times = [0.0], + loads = [0.0], + dt = 0.5, + G = 0.5*E/(1+nu), + yield_strength = 100.0, + # vm = sqrt(3)*G*ga | ea = ga + ea = 2*yield_strength/(sqrt(3)*G) -eeqs = [mat.variables.cumeq] -stresses = [copy(tovoigt(mat.variables.stress))] -for i=2:length(times) - dtime = times[i] - times[i-1] - dstrain31 = loads[i] - loads[i-1] - dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] - dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - mat.ddrivers = ChabocheDriverState(time=dtime, strain=dstrain_) - integrate_material!(mat) - update_material!(mat) - push!(stresses, copy(tovoigt(mat.variables.stress))) - push!(eeqs, mat.variables.cumeq) - # @info "time = $(mat.time), stress = $(mat.stress), cumeq = $(mat.properties.cumulative_equivalent_plastic_strain))" -end + # Go to elastic border + push!(times, times[end] + dt) + push!(loads, loads[end] + ea*dt) + # Proceed to plastic flow + push!(times, times[end] + dt) + push!(loads, loads[end] + ea*dt) + # Reverse direction + push!(times, times[end] + dt) + push!(loads, loads[end] - ea*dt) + # Continue and pass yield criterion + push!(times, times[end] + dt) + push!(loads, loads[end] - ea*dt) + push!(times, times[end] + dt) + push!(loads, loads[end] - ea*dt) -for i in 1:length(times) - @test isapprox(stresses[i][1:5], zeros(5); atol=1e-6) -end -s31 = [s[6] for s in stresses] + eeqs = [mat.variables.cumeq] + stresses = [copy(tovoigt(mat.variables.stress))] + for i=2:length(times) + dtime = times[i] - times[i-1] + dstrain31 = loads[i] - loads[i-1] + dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] + dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) + mat.ddrivers = ChabocheDriverState(time=dtime, strain=dstrain_) + integrate_material!(mat) + update_material!(mat) + push!(stresses, copy(tovoigt(mat.variables.stress))) + push!(eeqs, mat.variables.cumeq) + # @info "time = $(mat.time), stress = $(mat.stress), cumeq = $(mat.properties.cumulative_equivalent_plastic_strain))" + end -@test isapprox(s31[2], syield/sqrt(3.0)) -@test isapprox(s31[3]*sqrt(3.0), syield + 100.0*((eeqs[3]-eeqs[2])/dt)^(1.0/3.0); rtol=1e-2) -@test isapprox(s31[4], s31[3]-G*ea*dt) -@test isapprox(s31[6]*sqrt(3.0), -(syield + 100.0*((eeqs[6]-eeqs[5])/dt)^(1.0/3.0)); rtol=1e-2) + for i in 1:length(times) + @test isapprox(stresses[i][1:5], zeros(5); atol=1e-6) + end + s31 = [s[6] for s in stresses] + + @test isapprox(s31[2], yield_strength/sqrt(3.0)) + @test isapprox(s31[3]*sqrt(3.0), yield_strength + 100.0*((eeqs[3] - eeqs[2])/dt)^(1.0/3.0); rtol=1e-2) + @test isapprox(s31[4], s31[3] - G*ea*dt) + @test isapprox(s31[6]*sqrt(3.0), -(yield_strength + 100.0*((eeqs[6] - eeqs[5])/dt)^(1.0/3.0)); rtol=1e-2) +end diff --git a/test/test_idealplastic.jl b/test/test_idealplastic.jl index f1b3746..53ee91f 100644 --- a/test/test_idealplastic.jl +++ b/test/test_idealplastic.jl @@ -3,43 +3,50 @@ using Test, Tensors -parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, - poissons_ratio=0.3, - yield_stress=100.0) +let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, + poissons_ratio=0.3, + yield_stress=100.0), + epsilon=1e-3, + mat, # scope the name to this level; actual definition follows later + tostrain(vec) = fromvoigt(Symm2, vec; offdiagscale=2.0), + tostress(vec) = fromvoigt(Symm2, vec), + uniaxial_stress(sigma) = tostress([sigma, 0, 0, 0, 0, 0]) + let dtime=0.25 + dstrain_dtime = tostrain(epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) + ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) + mat = IdealPlastic{Float64}(parameters=parameters, ddrivers=ddrivers) + integrate_material!(mat) + update_material!(mat) + @test isapprox(mat.variables.stress, uniaxial_stress(50.0)) -dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) -ddrivers = IdealPlasticDriverState(time=0.25, strain=0.25*dstrain_dtime) -mat = IdealPlastic(parameters=parameters, ddrivers=ddrivers) -integrate_material!(mat) -update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(Symm2, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0])) + mat.ddrivers = ddrivers + integrate_material!(mat) + update_material!(mat) + @test isapprox(mat.variables.stress, uniaxial_stress(100.0)) + @test isapprox(mat.variables.cumeq, 0.0; atol=1.0e-12) -mat.ddrivers = ddrivers -integrate_material!(mat) -update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(Symm2, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) -@test isapprox(mat.variables.cumeq, 0.0; atol=1.0e-12) + dstrain_dtime = tostrain(epsilon*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]) + ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) + mat.ddrivers = ddrivers + integrate_material!(mat) + update_material!(mat) + @test isapprox(mat.variables.stress, uniaxial_stress(100.0); atol=1.0e-12) + @test isapprox(mat.variables.cumeq, dtime*epsilon) -dstrain_dtime = fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0) -ddrivers = IdealPlasticDriverState(time=0.25, strain=0.25*dstrain_dtime) -mat.ddrivers = ddrivers -integrate_material!(mat) -update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(Symm2, [100.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) -@test isapprox(mat.variables.cumeq, 0.25*1.0e-3) + dstrain_dtime = tostrain(-epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) + ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) + mat.ddrivers = ddrivers + integrate_material!(mat) + update_material!(mat) + @test isapprox(mat.variables.stress, uniaxial_stress(50.0); atol=1.0e-12) + end -dstrain_dtime = fromvoigt(Symm2{Float64}, -1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) -ddrivers = IdealPlasticDriverState(time=0.25, strain=0.25*dstrain_dtime) -mat.ddrivers = ddrivers -integrate_material!(mat) -update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(Symm2, [50.0, 0.0, 0.0, 0.0, 0.0, 0.0]); atol=1.0e-12) - -dstrain_dtime = (-0.75*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]; offdiagscale=2.0) - -0.25*fromvoigt(Symm2{Float64}, 1e-3*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]; offdiagscale=2.0)) -ddrivers = IdealPlasticDriverState(time=1.0, strain=dstrain_dtime) -mat.ddrivers = ddrivers -integrate_material!(mat) -integrate_material!(mat) -update_material!(mat) -@test isapprox(mat.variables.stress, fromvoigt(Symm2, [-100.0, 0.0, 0.0, 0.0, 0.0, 0.0])) + dstrain_dtime = (-0.75*tostrain(epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) + -0.25*tostrain(epsilon*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0])) + ddrivers = IdealPlasticDriverState(time=1.0, strain=dstrain_dtime*1.0) + mat.ddrivers = ddrivers + integrate_material!(mat) + integrate_material!(mat) + update_material!(mat) + @test isapprox(mat.variables.stress, uniaxial_stress(-100.0)) +end diff --git a/test/test_idealplastic_shear.jl b/test/test_idealplastic_shear.jl index 108cee5..0fc2f47 100644 --- a/test/test_idealplastic_shear.jl +++ b/test/test_idealplastic_shear.jl @@ -3,50 +3,51 @@ using Test, Tensors -E = 200.0e3 -nu = 0.3 -syield = 100.0 -parameters = IdealPlasticParameterState(youngs_modulus=E, - poissons_ratio=nu, - yield_stress=syield) +let E = 200.0e3, + nu = 0.3, + yield_stress = 100.0, + parameters = IdealPlasticParameterState(youngs_modulus=E, + poissons_ratio=nu, + yield_stress=yield_stress), + mat = IdealPlastic{Float64}(parameters=parameters), + times = [0.0], + loads = [0.0], + dt = 0.5, + G = 0.5*E/(1+nu), + # vm = sqrt(3)*G*ga | ea = ga + ea = 2*yield_stress/(sqrt(3)*G) -mat = IdealPlastic(parameters=parameters) + # Go to elastic border + push!(times, times[end] + dt) + push!(loads, loads[end] + ea*dt) + # Proceed to plastic flow + push!(times, times[end] + dt) + push!(loads, loads[end] + ea*dt) + # Reverse direction + push!(times, times[end] + dt) + push!(loads, loads[end] - ea*dt) + # Continue and pass yield criterion + push!(times, times[end] + dt) + push!(loads, loads[end] - 2*ea*dt) -times = [0.0] -loads = [0.0] -dt = 0.5 -G = 0.5*E/(1+nu) -# vm = sqrt(3)*G*ga | ea = ga -ea = 2*syield/(sqrt(3)*G) -# Go to elastic border -push!(times, times[end] + dt) -push!(loads, loads[end] + ea*dt) - # Proceed to plastic flow -push!(times, times[end] + dt) -push!(loads, loads[end] + ea*dt) - # Reverse direction -push!(times, times[end] + dt) -push!(loads, loads[end] - ea*dt) - # Continue and pass yield criterion -push!(times, times[end] + dt) -push!(loads, loads[end] - 2*ea*dt) -stresses = [copy(tovoigt(mat.variables.stress))] -for i=2:length(times) - dtime = times[i] - times[i-1] - dstrain31 = loads[i] - loads[i-1] - dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] - dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_) - mat.ddrivers = ddrivers - integrate_material!(mat) - update_material!(mat) - push!(stresses, copy(tovoigt(mat.variables.stress))) -end + stresses = [copy(tovoigt(mat.variables.stress))] + for i=2:length(times) + dtime = times[i] - times[i-1] + dstrain31 = loads[i] - loads[i-1] + dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] + dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) + ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_) + mat.ddrivers = ddrivers + integrate_material!(mat) + update_material!(mat) + push!(stresses, copy(tovoigt(mat.variables.stress))) + end -for i in 1:length(times) - @test isapprox(stresses[i][1:5], zeros(5); atol=1e-6) -end -s31 = [s[6] for s in stresses] + for i in 1:length(times) + @test isapprox(stresses[i][1:5], zeros(5); atol=1e-6) + end + s31 = [s[6] for s in stresses] -s31_expected = [0.0, syield/sqrt(3.0), syield/sqrt(3.0), 0.0, -syield/sqrt(3.0)] -@test isapprox(s31, s31_expected; rtol=1.0e-2) + s31_expected = [0.0, yield_stress/sqrt(3.0), yield_stress/sqrt(3.0), 0.0, -yield_stress/sqrt(3.0)] + @test isapprox(s31, s31_expected; rtol=1.0e-2) +end diff --git a/test/test_stress_driven_uniaxial_increment.jl b/test/test_stress_driven_uniaxial_increment.jl index 1ad71ee..9701478 100644 --- a/test/test_stress_driven_uniaxial_increment.jl +++ b/test/test_stress_driven_uniaxial_increment.jl @@ -13,7 +13,7 @@ parameters = ChabocheParameterState(E=200.0e3, D2=1000.0, Q=0.0, b=0.1) -material = Chaboche(parameters=parameters) +material = Chaboche{Float64}(parameters=parameters) dtime = 0.25 times = [material.drivers.time] diff --git a/test/test_uniaxial_increment.jl b/test/test_uniaxial_increment.jl index 972bb40..44980ae 100644 --- a/test/test_uniaxial_increment.jl +++ b/test/test_uniaxial_increment.jl @@ -3,34 +3,34 @@ using Test, Tensors -dtime = 0.25 -parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, - poissons_ratio=0.3, - yield_stress=100.0) -mat = IdealPlastic(parameters=parameters) -# times = [mat.drivers.time] -# stresses = [copy(tovoigt(mat.variables.stress))] -stresses_expected = [[50.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [100.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [100.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [50.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [-100.0, 0.0, 0.0, 0.0, 0.0, 0.0]] -dstrain11 = 1e-3*dtime -strains_expected = [[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], - [2*dstrain11, -0.3*dstrain11*2, -0.3*dstrain11*2, 0.0, 0.0, 0.0], - [3*dstrain11, -0.3*dstrain11*2 - 0.5*dstrain11, -0.3*dstrain11*2 - 0.5*dstrain11, 0.0, 0.0, 0.0], - [2*dstrain11, -0.3*dstrain11 - 0.5*dstrain11, -0.3*dstrain11 - 0.5*dstrain11, 0.0, 0.0, 0.0], - [-2*dstrain11, 0.3*dstrain11*2, 0.3*dstrain11*2, 0.0, 0.0, 0.0]] -dtimes = [dtime, dtime, dtime, dtime, 1.0] -dstrains11 = [dstrain11, dstrain11, dstrain11, -dstrain11, -4*dstrain11] -for i in 1:length(dtimes) - dstrain11 = dstrains11[i] - dtime = dtimes[i] - uniaxial_increment!(mat, dstrain11, dtime) - update_material!(mat) - # push!(times, mat.drivers.time) - # push!(stresses, copy(tovoigt(mat.variables.stress))) - #@info(tovoigt(mat.variables.stress), stresses_expected[i]) - @test isapprox(tovoigt(mat.variables.stress), stresses_expected[i]) - @test isapprox(tovoigt(mat.drivers.strain; offdiagscale=2.0), strains_expected[i]) +let dtime = 0.25, + parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, + poissons_ratio=0.3, + yield_stress=100.0), + mat = IdealPlastic{Float64}(parameters=parameters), + tostrain(vec) = fromvoigt(Symm2, vec; offdiagscale=2.0), + tostress(vec) = fromvoigt(Symm2, vec), + uniaxial_stress(sigma) = tostress([sigma, 0, 0, 0, 0, 0]), + stresses_expected = [uniaxial_stress(50.0), + uniaxial_stress(100.0), + uniaxial_stress(100.0), + uniaxial_stress(50.0), + uniaxial_stress(-100.0)], + dstrain11 = 1e-3*dtime, + strains_expected = [tostrain(dstrain11*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]), + tostrain(dstrain11*[2, -0.3*2, -0.3*2, 0.0, 0.0, 0.0]), + tostrain(dstrain11*[3, -0.3*2 - 0.5, -0.3*2 - 0.5, 0.0, 0.0, 0.0]), + tostrain(dstrain11*[2, -0.3 - 0.5, -0.3 - 0.5, 0.0, 0.0, 0.0]), + tostrain(dstrain11*[-2, 0.3*2, 0.3*2, 0.0, 0.0, 0.0])], + dtimes = [dtime, dtime, dtime, dtime, 1.0], + dstrains11 = dstrain11*[1.0, 1.0, 1.0, -1.0, -4.0] + + for i in 1:length(dtimes) + dstrain11 = dstrains11[i] + dtime = dtimes[i] + uniaxial_increment!(mat, dstrain11, dtime) + update_material!(mat) + @test isapprox(mat.variables.stress, stresses_expected[i]) + @test isapprox(mat.drivers.strain, strains_expected[i]) + end end diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 836665b..fcc284f 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -43,22 +43,26 @@ end for (result, expected) in zip(delame(lame(1e11, 0.3)...), (1e11, 0.3))) # Mutating function to non-mutating function conversion -function test_debang() # introduce a local scope so the names `f!`, `f` and `out` are local to this test. +let # introduce a local scope so the name `f!` is only defined locally for this test. function f!(out, x) out[:] = [sin(elt) for elt in x] return nothing end - out = [0.0] - @test all([f!(out, [pi/4]) == nothing, - isapprox(out, [1/sqrt(2)])]) - f = debang(f!) - @test f isa Function - out = [0.0] - @test all([isapprox(f([pi/4]), [1/sqrt(2)]), - out == [0.0]]) + let + out = [0.0] + @test all([f!(out, [pi/4]) == nothing, + isapprox(out, [1/sqrt(2)])]) + end + + let + out = [0.0] + f = debang(f!) + @test f isa Function + @test all([isapprox(f([pi/4]), [1/sqrt(2)]), + out == [0.0]]) + end end -test_debang() # Newton root finder let g(x) = [(1 - x[1]^2) + x[2]], From 6811aaa6fef0245f48a0f0566992eecfa57e903b Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:50:02 +0300 Subject: [PATCH 41/66] Small cleanup --- test/test_biaxial_increment.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index 1b1392c..d9db428 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -22,7 +22,7 @@ let dtime = 0.25, dstrains11 = dstrain11*[1.0, 1.0, 1.0, -1.0, -4.0], dstrains12 = dstrain12*[1.0, 1.0, 1.0, -1.0, -4.0] - plastic_flow_occurred = zeros(length(dstrains11) - 1) + plastic_flow_occurred = zeros(Bool, length(dstrains11) - 1) for i in 1:length(dtimes) dstrain11 = dstrains11[i] dstrain12 = dstrains12[i] @@ -33,7 +33,7 @@ let dtime = 0.25, plastic_flow_occurred[i-1] = (mat.variables.cumeq > 0.0) end @test !iszero(mat.variables.stress[1,1]) && !iszero(mat.variables.stress[1,2]) - @test isapprox(tovoigt(mat.variables.stress; offdiagscale=2.0)[2:5],zeros(4); atol=1e-5) + @test isapprox(tovoigt(mat.variables.stress; offdiagscale=2.0)[2:5], zeros(4); atol=1e-5) end - @test any(x->x==1, plastic_flow_occurred) + @test any(plastic_flow_occurred) end From 3c71211383c55c0586577f59d10028016571a30c Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:50:36 +0300 Subject: [PATCH 42/66] Clean up tests --- test/test_stress_driven_uniaxial_increment.jl | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/test/test_stress_driven_uniaxial_increment.jl b/test/test_stress_driven_uniaxial_increment.jl index 9701478..0a4f298 100644 --- a/test/test_stress_driven_uniaxial_increment.jl +++ b/test/test_stress_driven_uniaxial_increment.jl @@ -1,52 +1,54 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE -using Test, Tensors, Materials -parameters = ChabocheParameterState(E=200.0e3, - nu=0.3, - R0=100.0, - Kn=100.0, - nn=10.0, - C1=10000.0, - D1=100.0, - C2=50000.0, - D2=1000.0, - Q=0.0, - b=0.1) -material = Chaboche{Float64}(parameters=parameters) +using Test, Tensors -dtime = 0.25 -times = [material.drivers.time] -stresses = [copy(tovoigt(material.variables.stress))] -strains = [copy(tovoigt(material.drivers.strain; offdiagscale=2.0))] -cumeqs = [copy(material.variables.cumeq)] +let dtime = 0.25, + parameters = ChabocheParameterState(E=200.0e3, + nu=0.3, + R0=100.0, + Kn=100.0, + nn=10.0, + C1=10000.0, + D1=100.0, + C2=50000.0, + D2=1000.0, + Q=0.0, + b=0.1), + material = Chaboche{Float64}(parameters=parameters), + times = [material.drivers.time], + stresses = [copy(tovoigt(material.variables.stress))], + strains = [copy(tovoigt(material.drivers.strain; offdiagscale=2.0))], + cumeqs = [copy(material.variables.cumeq)], + tostrain(vec) = fromvoigt(Symm2, vec; offdiagscale=2.0), + tostress(vec) = fromvoigt(Symm2, vec), + uniaxial_stress(sigma) = tostress([sigma, 0, 0, 0, 0, 0]), + stresses_expected = [uniaxial_stress(50.0), + uniaxial_stress(100.0), + uniaxial_stress(150.0), + uniaxial_stress(150.0), + uniaxial_stress(100.0), + uniaxial_stress(-100.0)], + dstress = 50.0, + dstresses11 = dstress*[1.0, 1.0, 1.0, 0.0, -1.0, -4.0] + dtimes = [dtime, dtime, dtime, 1e3, dtime, 1e3] -stresses_expected = [[ 50.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [ 100.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [ 150.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [ 150.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [ 100.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [ -100.0, 0.0, 0.0, 0.0, 0.0, 0.0]] -dstress = 50.0 + for i in 1:length(dtimes) + dstress11 = dstresses11[i] + dtime = dtimes[i] + stress_driven_uniaxial_increment!(material, dstress11, dtime) + update_material!(material) + push!(times, material.drivers.time) + push!(stresses, copy(tovoigt(material.variables.stress))) + push!(strains, copy(tovoigt(material.drivers.strain; offdiagscale=2.0))) + push!(cumeqs, copy(material.variables.cumeq)) + @test isapprox(material.variables.stress, stresses_expected[i]; atol=1e-4) + end -dtimes = [dtime, dtime, dtime, 1e3, dtime, 1e3] -dstresses11 = [dstress, dstress, dstress, 0.0, -dstress, -4*dstress] + dstrain_creep = strains[5] - strains[4] + @test isapprox(dstrain_creep[2], -dstrain_creep[1]*0.5; atol=1e-4) + @test isapprox(dstrain_creep[3], -dstrain_creep[1]*0.5; atol=1e-4) -for i in 1:length(dtimes) - dstress11 = dstresses11[i] - dtime = dtimes[i] - stress_driven_uniaxial_increment!(material, dstress11, dtime) - update_material!(material) - push!(times, material.drivers.time) - push!(stresses, copy(tovoigt(material.variables.stress))) - push!(strains, copy(tovoigt(material.drivers.strain; offdiagscale=2.0))) - push!(cumeqs, copy(material.variables.cumeq)) - @test isapprox(tovoigt(material.variables.stress), stresses_expected[i]; atol=1e-4) + dcumeq = cumeqs[end] - cumeqs[end-1] + @test dcumeq > 0 end - -dstrain_creep = strains[5] - strains[4] -@test isapprox(dstrain_creep[2], -dstrain_creep[1]*0.5; atol=1e-4) -@test isapprox(dstrain_creep[3], -dstrain_creep[1]*0.5; atol=1e-4) - -dcumeq = cumeqs[end] - cumeqs[end-1] -@test dcumeq > 0 From 7be4c2e767af0ac4045a4a2af006719e05a7eba3 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 10 Jun 2020 13:50:49 +0300 Subject: [PATCH 43/66] Mark TODOs for convergence failures in tests --- test/test_biaxial_increment.jl | 3 ++- test/test_stress_driven_uniaxial_increment.jl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index d9db428..11f39e5 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -27,7 +27,8 @@ let dtime = 0.25, dstrain11 = dstrains11[i] dstrain12 = dstrains12[i] dtime = dtimes[i] - biaxial_increment!(mat, dstrain11, dstrain12, dtime) + # TODO: After i > 1, never converges to within 1e-9. Figure out why. + biaxial_increment!(mat, dstrain11, dstrain12, dtime; norm_acc=1e-8) update_material!(mat) if i > 1 plastic_flow_occurred[i-1] = (mat.variables.cumeq > 0.0) diff --git a/test/test_stress_driven_uniaxial_increment.jl b/test/test_stress_driven_uniaxial_increment.jl index 0a4f298..274e46e 100644 --- a/test/test_stress_driven_uniaxial_increment.jl +++ b/test/test_stress_driven_uniaxial_increment.jl @@ -36,6 +36,7 @@ let dtime = 0.25, for i in 1:length(dtimes) dstress11 = dstresses11[i] dtime = dtimes[i] + # TODO: Does not converge at i = 4. Figure out why. stress_driven_uniaxial_increment!(material, dstress11, dtime) update_material!(material) push!(times, material.drivers.time) From 5aed2ec75391e56b027cb146837fd4137b4d03a2 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 11:00:48 +0300 Subject: [PATCH 44/66] Clearer name for `dstr` -> `dstrain_correction` in optimizer --- src/increments.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/increments.jl b/src/increments.jl index 0a8bbb3..856efca 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -89,9 +89,9 @@ function uniaxial_increment!(material::AbstractMaterial, dstrain11::Real, dt::Re dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], max_iter::Integer=50, norm_acc::Real=1e-9) function update_dstrain!(dstrain::V, dstress::V, jacobian::AbstractArray{T}) where V <: AbstractVector{T} where T <: Real - dstr = -jacobian[2:end,2:end] \ dstress[2:end] - dstrain[2:end] .+= dstr - return norm(dstr) + dstrain_correction = -jacobian[2:end,2:end] \ dstress[2:end] + dstrain[2:end] .+= dstrain_correction + return norm(dstrain_correction) end optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing @@ -115,9 +115,9 @@ function biaxial_increment!(material::AbstractMaterial, dstrain11::Real, dstrain dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], max_iter::Integer=50, norm_acc::Real=1e-9) function update_dstrain!(dstrain::V, dstress::V, jacobian::AbstractArray{T}) where V <: AbstractVector{T} where T <: Real - dstr = -jacobian[2:end-1,2:end-1] \ dstress[2:end-1] - dstrain[2:end-1] .+= dstr - return norm(dstr) + dstrain_correction = -jacobian[2:end-1,2:end-1] \ dstress[2:end-1] + dstrain[2:end-1] .+= dstrain_correction + return norm(dstrain_correction) end optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing @@ -143,9 +143,9 @@ function stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11 # Mutation of `dstress` doesn't matter, since `dstress` is freshly generated at each iteration. # The lexical closure property gives us access to `dstress11` in this scope. dstress[1] -= dstress11 - dstr = -jacobian \ dstress - dstrain .+= dstr - return norm(dstr) + dstrain_correction = -jacobian \ dstress + dstrain .+= dstrain_correction + return norm(dstrain_correction) end optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing From a7f27ef59a0e2e2e95c3b12f78410853c6f25e86 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 11:01:11 +0300 Subject: [PATCH 45/66] Add some commented-out debug messages --- src/increments.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/increments.jl b/src/increments.jl index 856efca..c4b63ef 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -56,7 +56,9 @@ function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{< max_iter::Integer=50, tol::Real=1e-9) stress0 = tovoigt(material.variables.stress) # observed T = typeof(dstrain[1]) + # @debug "---START---" for i=1:max_iter + # @debug "$i, $dstrain, $stress0, $(material.variables.stress)" material.ddrivers.time = dt material.ddrivers.strain = fromvoigt(Symm2{T}, dstrain; offdiagscale=2.0) integrate_material!(material) From fda4ce1b8b4876cf721002600d3c43f56767d8a2 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 11:01:35 +0300 Subject: [PATCH 46/66] With the seemingly wrong jacobian, the tests work. Figure out why... Could be the test data was generated by this solver itself? Either that, or I'm totally missing something obvious as to the minus sign. --- src/chaboche.jl | 2 +- test/test_biaxial_increment.jl | 3 +-- test/test_stress_driven_uniaxial_increment.jl | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 9cd9a67..ea1b6a3 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -151,7 +151,7 @@ function integrate_material!(material::Chaboche{T}) where T <: Real # the other factor we will have to supply manually. drdx = ForwardDiff.jacobian(debang(g!), x) # Array{19, 19} drde = zeros((length(x),6)) # Array{19, 6} - drde[1:6, 1:6] = tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. + drde[1:6, 1:6] = -tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end variables_new = ChabocheVariableState(stress = stress, diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index 11f39e5..d9db428 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -27,8 +27,7 @@ let dtime = 0.25, dstrain11 = dstrains11[i] dstrain12 = dstrains12[i] dtime = dtimes[i] - # TODO: After i > 1, never converges to within 1e-9. Figure out why. - biaxial_increment!(mat, dstrain11, dstrain12, dtime; norm_acc=1e-8) + biaxial_increment!(mat, dstrain11, dstrain12, dtime) update_material!(mat) if i > 1 plastic_flow_occurred[i-1] = (mat.variables.cumeq > 0.0) diff --git a/test/test_stress_driven_uniaxial_increment.jl b/test/test_stress_driven_uniaxial_increment.jl index 274e46e..0a4f298 100644 --- a/test/test_stress_driven_uniaxial_increment.jl +++ b/test/test_stress_driven_uniaxial_increment.jl @@ -36,7 +36,6 @@ let dtime = 0.25, for i in 1:length(dtimes) dstress11 = dstresses11[i] dtime = dtimes[i] - # TODO: Does not converge at i = 4. Figure out why. stress_driven_uniaxial_increment!(material, dstress11, dtime) update_material!(material) push!(times, material.drivers.time) From 05630b5daf8438c4c95d928cc10e7a24bbc18306 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 13:47:09 +0300 Subject: [PATCH 47/66] Be explicit about "return nothing" in g! --- src/chaboche.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chaboche.jl b/src/chaboche.jl index ea1b6a3..e96e4ab 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -242,6 +242,7 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: F[7] = R - R_ + b*(Q - R_)*dp tovoigt!(view(F, 8:13), X1 - X1_ + dp*(2.0/3.0*C1*n - D1*X1_)) tovoigt!(view(F, 14:19), X2 - X2_ + dp*(2.0/3.0*C2*n - D2*X2_)) + return nothing end return g! end From 0718a36d5247d34470880e53c24bc6745aa80c16 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 16:52:25 +0300 Subject: [PATCH 48/66] Improve documentation, variable naming --- src/chaboche.jl | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index e96e4ab..2b09e27 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -24,7 +24,7 @@ The classical viscoplastic material is a special case of this model with `C1 = C `E`: Young's modulus `nu`: Poisson's ratio `R0`: initial yield strength -`Kn`: plasticity multiplier divisor +`Kn`: plasticity multiplier divisor (drag stress) `nn`: plasticity multiplier exponent `C1`, `D1`: parameters governing behavior of backstress X1 `C2`, `D2`: parameters governing behavior of backstress X2 @@ -120,8 +120,9 @@ function integrate_material!(material::Chaboche{T}) where T <: Real # resulting deviatoric plastic stress (accounting for backstresses Xm) seff_dev = dev(stress - X1 - X2) - # von Mises yield function; f := J(seff_dev) - Y + # von Mises yield function f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using elastic trial problem state + isplastic = (f > 0.0) if f > 0.0 g! = create_nonlinear_system_of_equations(material) x0 = state_to_vector(stress, R, X1, X2) @@ -144,16 +145,18 @@ function integrate_material!(material::Chaboche{T}) where T <: Real # Compute the new Jacobian, accounting for the plastic contribution. Because # x ≡ [σ R X1 X2] (vector of length 19, with tensors encoded in Voigt format) # we have - # (dx/dε)[1:6,1:6] = dσ/dε + # dσ/dε = (dx/dε)[1:6,1:6] # for which we can compute the LHS as follows: # dx/dε = dx/dr dr/dε = inv(dr/dx) dr/dε ≡ (dr/dx) \ (dr/dε) # where r = r(x) is the residual, given by the function g!. AD can get us dr/dx automatically, # the other factor we will have to supply manually. drdx = ForwardDiff.jacobian(debang(g!), x) # Array{19, 19} drde = zeros((length(x),6)) # Array{19, 6} - drde[1:6, 1:6] = -tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. + drde[1:6, 1:6] = tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end + # @info """$(f > 0.0 ? "plastic" : "elastic")\n$(tovoigt(jacobian))\n\n""" + @info """$(isplastic ? "plastic" : "elastic")\n""" variables_new = ChabocheVariableState(stress = stress, X1 = X1, X2 = X2, @@ -212,10 +215,10 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: # Compute the residual. F is output, x is filled by NLsolve. # The solution is x = x* such that g(x*) = 0. function g!(F::V, x::V) where V <: AbstractVector{<:Real} - stress_, R_, X1_, X2_ = state_from_vector(x) # tentative new values from nlsolve + stress_new, R_new, X1_new, X2_new = state_from_vector(x) # tentative new values from nlsolve - seff_dev = dev(stress_ - X1_ - X2_) - f = sqrt(1.5)*norm(seff_dev) - (R0 + R_) + seff_dev = dev(stress_new - X1_new - X2_new) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R_new) dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn dp = dotp*dtime @@ -223,7 +226,7 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: # The equations are written in a delta form: # - # Δσ = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) + # Δσ = stress_new - stress = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) # ΔR = b (Q - R_new) |dε_p| (component 7) # ΔX1 = (2/3) C1 |dε_p| (n - (3/2) (D1/C1) X1_new) (components 8:13) # ΔX2 = (2/3) C2 |dε_p| (n - (3/2) (D2/C2) X2_new) (components 14:19) @@ -238,10 +241,10 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: # dstrain_plastic = dp*n dstrain_elastic = dstrain - dstrain_plastic - tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain_elastic)) - F[7] = R - R_ + b*(Q - R_)*dp - tovoigt!(view(F, 8:13), X1 - X1_ + dp*(2.0/3.0*C1*n - D1*X1_)) - tovoigt!(view(F, 14:19), X2 - X2_ + dp*(2.0/3.0*C2*n - D2*X2_)) + tovoigt!(view(F, 1:6), stress_new - stress - dcontract(jacobian, dstrain_elastic)) + F[7] = R_new - R - b*(Q - R_new)*dp + tovoigt!(view(F, 8:13), X1_new - X1 - dp*(2.0/3.0*C1*n - D1*X1_new)) + tovoigt!(view(F, 14:19), X2_new - X2 - dp*(2.0/3.0*C2*n - D2*X2_new)) return nothing end return g! From 55aa1a9cb6889d1b461dfc56ff42910410c86890 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 16:52:38 +0300 Subject: [PATCH 49/66] Fix stray offdiagscale in biaxial increment test --- test/test_biaxial_increment.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index d9db428..2cbf563 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -33,7 +33,7 @@ let dtime = 0.25, plastic_flow_occurred[i-1] = (mat.variables.cumeq > 0.0) end @test !iszero(mat.variables.stress[1,1]) && !iszero(mat.variables.stress[1,2]) - @test isapprox(tovoigt(mat.variables.stress; offdiagscale=2.0)[2:5], zeros(4); atol=1e-5) + @test isapprox(tovoigt(mat.variables.stress)[2:5], zeros(4); atol=1e-8) end @test any(plastic_flow_occurred) end From f3f5a6fc796b65fc2108393ec062f48729264fb7 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 11 Jun 2020 16:52:52 +0300 Subject: [PATCH 50/66] Clean up Chaboche shear test (Thanks, Reijo!) --- test/test_chaboche_shear.jl | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/test/test_chaboche_shear.jl b/test/test_chaboche_shear.jl index 10d5430..58765f2 100644 --- a/test/test_chaboche_shear.jl +++ b/test/test_chaboche_shear.jl @@ -7,7 +7,7 @@ let E = 200.0e3, nu = 0.3, parameters = ChabocheParameterState(E=E, nu=nu, - R0=100.0, + R0=100.0, # yield in shear = R0 / sqrt(3) Kn=100.0, nn=3.0, C1=0.0, @@ -19,36 +19,39 @@ let E = 200.0e3, mat = Chaboche{Float64}(parameters=parameters), times = [0.0], loads = [0.0], - dt = 0.5, + dt = 1.0, G = 0.5*E/(1+nu), yield_strength = 100.0, - # vm = sqrt(3)*G*ga | ea = ga - ea = 2*yield_strength/(sqrt(3)*G) + # vonMises = sqrt(3 J_2) = sqrt(3/2 tr(s^2)) = sqrt(3) |tau| = sqrt(3)*G*|gamma| + # gamma = 2 e12 + # set vonMises = Y + gamma_yield = yield_strength/(sqrt(3)*G) # Go to elastic border push!(times, times[end] + dt) - push!(loads, loads[end] + ea*dt) + push!(loads, loads[end] + gamma_yield*dt) # Proceed to plastic flow push!(times, times[end] + dt) - push!(loads, loads[end] + ea*dt) + push!(loads, loads[end] + gamma_yield*dt) # Reverse direction push!(times, times[end] + dt) - push!(loads, loads[end] - ea*dt) + push!(loads, loads[end] - gamma_yield*dt) # Continue and pass yield criterion push!(times, times[end] + dt) - push!(loads, loads[end] - ea*dt) + push!(loads, loads[end] - gamma_yield*dt) push!(times, times[end] + dt) - push!(loads, loads[end] - ea*dt) + push!(loads, loads[end] - gamma_yield*dt) eeqs = [mat.variables.cumeq] stresses = [copy(tovoigt(mat.variables.stress))] for i=2:length(times) dtime = times[i] - times[i-1] - dstrain31 = loads[i] - loads[i-1] - dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] - dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - mat.ddrivers = ChabocheDriverState(time=dtime, strain=dstrain_) + dstrain12 = loads[i] - loads[i-1] + dstrain_voigt = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain12] + dstrain_tensor = fromvoigt(Symm2{Float64}, dstrain_voigt; offdiagscale=2.0) + mat.ddrivers = ChabocheDriverState(time=dtime, strain=dstrain_tensor) integrate_material!(mat) + # @info "$i, $gamma_yield, $(mat.variables_new.stress[1,2]), $(2.0*mat.variables_new.plastic_strain[1,2])\n" update_material!(mat) push!(stresses, copy(tovoigt(mat.variables.stress))) push!(eeqs, mat.variables.cumeq) @@ -62,6 +65,6 @@ let E = 200.0e3, @test isapprox(s31[2], yield_strength/sqrt(3.0)) @test isapprox(s31[3]*sqrt(3.0), yield_strength + 100.0*((eeqs[3] - eeqs[2])/dt)^(1.0/3.0); rtol=1e-2) - @test isapprox(s31[4], s31[3] - G*ea*dt) + @test isapprox(s31[4], s31[3] - G*gamma_yield*dt) @test isapprox(s31[6]*sqrt(3.0), -(yield_strength + 100.0*((eeqs[6] - eeqs[5])/dt)^(1.0/3.0)); rtol=1e-2) end From 5d75f903ccb68208f44f42ce115414367acaf658 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 12 Jun 2020 14:34:55 +0300 Subject: [PATCH 51/66] Update comments concerning the jacobian --- src/chaboche.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 2b09e27..302f19f 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -226,7 +226,7 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: # The equations are written in a delta form: # - # Δσ = stress_new - stress = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) + # Δσ = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) # ΔR = b (Q - R_new) |dε_p| (component 7) # ΔX1 = (2/3) C1 |dε_p| (n - (3/2) (D1/C1) X1_new) (components 8:13) # ΔX2 = (2/3) C2 |dε_p| (n - (3/2) (D2/C2) X2_new) (components 14:19) @@ -235,7 +235,7 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: # # Δ(...) = (...)_new - (...)_old # - # Then move the delta terms to the RHS to get the standard form, (stuff) = 0. + # Then move the terms on the RHS to the LHS to get the standard form, (stuff) = 0. # Also, below we avoid the multiplication and division that cancel each other # in the last terms of the equations for ΔX1 and ΔX2. # From f93776ff5d30d30620354a8b2525180c2d1a3778 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 12 Jun 2020 14:35:27 +0300 Subject: [PATCH 52/66] Oops, debug print --- src/chaboche.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 302f19f..414e641 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -156,7 +156,7 @@ function integrate_material!(material::Chaboche{T}) where T <: Real jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end # @info """$(f > 0.0 ? "plastic" : "elastic")\n$(tovoigt(jacobian))\n\n""" - @info """$(isplastic ? "plastic" : "elastic")\n""" + # @info """$(isplastic ? "plastic" : "elastic")\n""" variables_new = ChabocheVariableState(stress = stress, X1 = X1, X2 = X2, From 024b5eba52643751bdd894b998ade7948347153c Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 3 Aug 2020 12:40:55 +0300 Subject: [PATCH 53/66] Add ChabocheOptions, so far with a single property: nlsolve_method As per discussion with Reijo, it would be useful to be able to choose the method used by NLsolve for solving the nonlinear equation system. Since the nonlinear equation system is solved in a tight loop (especially considering FEM), this allows the user to increase performance by choosing a faster method when that is appropriate for the problem being solved. TODO later: we should also think about which method to use as the default. trust_region is NLsolve's own default. It is robust, but typically requires a large number of evaluations of the residual function. Erring on the side of caution is good numerics, so the practical question is: is there another method that is at least equally robust, but provides higher performance? --- src/chaboche.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 414e641..89839ab 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -65,6 +65,10 @@ end jacobian::Symm4{T} = zero(Symm4{T}) end +@with_kw struct ChabocheOptions <: AbstractMaterialState + nlsolve_method::Symbol = :trust_region +end + @with_kw mutable struct Chaboche{T <: Real} <: AbstractMaterial drivers::ChabocheDriverState{T} = ChabocheDriverState{T}() ddrivers::ChabocheDriverState{T} = ChabocheDriverState{T}() @@ -72,6 +76,7 @@ end variables_new::ChabocheVariableState{T} = ChabocheVariableState{T}() parameters::ChabocheParameterState{T} = ChabocheParameterState{T}() dparameters::ChabocheParameterState{T} = ChabocheParameterState{T}() + options::ChabocheOptions = ChabocheOptions() end """ @@ -126,7 +131,7 @@ function integrate_material!(material::Chaboche{T}) where T <: Real if f > 0.0 g! = create_nonlinear_system_of_equations(material) x0 = state_to_vector(stress, R, X1, X2) - res = nlsolve(g!, x0; autodiff=:forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl + res = nlsolve(g!, x0; method=mat.options.nlsolve_method, autodiff=:forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl converged(res) || error("Nonlinear system of equations did not converge!") x = res.zero stress, R, X1, X2 = state_from_vector(x) From 0e3a8b748c0e1927cdbcece924d9d7f0277cb617 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 6 Aug 2020 11:35:40 +0300 Subject: [PATCH 54/66] Remove some accidentally remaining debug stuff --- src/chaboche.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 89839ab..ff6d467 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -127,7 +127,6 @@ function integrate_material!(material::Chaboche{T}) where T <: Real seff_dev = dev(stress - X1 - X2) # von Mises yield function f = sqrt(1.5)*norm(seff_dev) - (R0 + R) # using elastic trial problem state - isplastic = (f > 0.0) if f > 0.0 g! = create_nonlinear_system_of_equations(material) x0 = state_to_vector(stress, R, X1, X2) @@ -160,8 +159,6 @@ function integrate_material!(material::Chaboche{T}) where T <: Real drde[1:6, 1:6] = tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end - # @info """$(f > 0.0 ? "plastic" : "elastic")\n$(tovoigt(jacobian))\n\n""" - # @info """$(isplastic ? "plastic" : "elastic")\n""" variables_new = ChabocheVariableState(stress = stress, X1 = X1, X2 = X2, From 43bebb405abbb7f70d292cc03dbd19c46a845f46 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 6 Aug 2020 15:13:57 +0300 Subject: [PATCH 55/66] =?UTF-8?q?Add=20note=20that=20n=20=3D=20=E2=88=82f/?= =?UTF-8?q?=E2=88=82=CF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chaboche.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index ff6d467..4f28d50 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -141,7 +141,7 @@ function integrate_material!(material::Chaboche{T}) where T <: Real dotp = ((f >= 0.0 ? f : 0.0)/Kn)^nn # plasticity multiplier, see equations (3) and (4) in Chaboche 2013 dp = dotp*dtime # |dε_p|, using backward Euler (dotp is ∂ε_p/∂t at the end of the timestep) - n = sqrt(1.5)*seff_dev/norm(seff_dev) # Chaboche: a (tensorial) unit direction, s.t. 2/3 * (n : n) = 1 + n = sqrt(1.5)*seff_dev/norm(seff_dev) # Chaboche: a (tensorial) unit direction, s.t. 2/3 * (n : n) = 1; also n = ∂f/∂σ. plastic_strain += dp*n cumeq += dp # cumulative equivalent plastic strain (note dp ≥ 0) From 4ae34ed2ff0b833d9bfedc2d6ff55239a0f59f11 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 6 Aug 2020 15:48:44 +0300 Subject: [PATCH 56/66] Terminology: delta form -> incremental form --- src/chaboche.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 4f28d50..a098b21 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -173,8 +173,8 @@ end """ create_nonlinear_system_of_equations(material::Chaboche) -Create and return an instance of the equation system for the delta form of the -evolution equations of the Chaboche material. +Create and return an instance of the equation system for the incremental form of +the evolution equations of the Chaboche material. Used internally for computing the plastic contribution in `integrate_material!`. @@ -226,7 +226,7 @@ function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: dp = dotp*dtime n = sqrt(1.5)*seff_dev/norm(seff_dev) - # The equations are written in a delta form: + # The equations are written in an incremental form: # # Δσ = (∂σ/∂ε)_e : dε_e = (∂σ/∂ε)_e : (dε - dε_p) (components 1:6) # ΔR = b (Q - R_new) |dε_p| (component 7) From 395d20add4554b7d66147fbf6284d47a136c8c8e Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 6 Aug 2020 15:51:21 +0300 Subject: [PATCH 57/66] fix a typoed variable name --- src/chaboche.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index a098b21..6eed217 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -130,7 +130,7 @@ function integrate_material!(material::Chaboche{T}) where T <: Real if f > 0.0 g! = create_nonlinear_system_of_equations(material) x0 = state_to_vector(stress, R, X1, X2) - res = nlsolve(g!, x0; method=mat.options.nlsolve_method, autodiff=:forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl + res = nlsolve(g!, x0; method=material.options.nlsolve_method, autodiff=:forward) # user manual: https://github.com/JuliaNLSolvers/NLsolve.jl converged(res) || error("Nonlinear system of equations did not converge!") x = res.zero stress, R, X1, X2 = state_from_vector(x) From d2e9feca464177d61445b98d857a8dd5be5d8313 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Thu, 6 Aug 2020 15:54:34 +0300 Subject: [PATCH 58/66] Resolve the parametric polymorphism bikeshed @with_kw mutable struct GenericChaboche{T <: Real} <: AbstractMaterial ... end Chaboche = GenericChaboche{Float64} Other data structures changed similarly. --- src/chaboche.jl | 38 ++++++++++++------- src/idealplastic.jl | 37 +++++++++++------- test/test_biaxial_increment.jl | 2 +- test/test_chaboche.jl | 2 +- test/test_chaboche_shear.jl | 2 +- test/test_idealplastic.jl | 2 +- test/test_idealplastic_shear.jl | 2 +- test/test_stress_driven_uniaxial_increment.jl | 2 +- test/test_uniaxial_increment.jl | 2 +- 9 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index 6eed217..8383046 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -9,9 +9,13 @@ import ..AbstractMaterial, ..AbstractMaterialState import ..Utilities: Symm2, Symm4, isotropic_elasticity_tensor, lame, debang import ..integrate_material! # for method extension +# parametrically polymorphic for any type representing ℝ +export GenericChaboche, GenericChabocheDriverState, GenericChabocheParameterState, GenericChabocheVariableState + +# specialization for Float64 export Chaboche, ChabocheDriverState, ChabocheParameterState, ChabocheVariableState -@with_kw mutable struct ChabocheDriverState{T <: Real} <: AbstractMaterialState +@with_kw mutable struct GenericChabocheDriverState{T <: Real} <: AbstractMaterialState time::T = zero(T) strain::Symm2{T} = zero(Symm2{T}) end @@ -31,7 +35,7 @@ The classical viscoplastic material is a special case of this model with `C1 = C `Q`: shift parameter for yield strength evolution `b`: multiplier for yield strength evolution """ -@with_kw struct ChabocheParameterState{T <: Real} <: AbstractMaterialState +@with_kw struct GenericChabocheParameterState{T <: Real} <: AbstractMaterialState E::T = 0 nu::T = 0 R0::T = 0 @@ -55,7 +59,7 @@ end `R`: yield strength `jacobian`: ∂σij/∂εkl """ -@with_kw struct ChabocheVariableState{T <: Real} <: AbstractMaterialState +@with_kw struct GenericChabocheVariableState{T <: Real} <: AbstractMaterialState stress::Symm2{T} = zero(Symm2{T}) X1::Symm2{T} = zero(Symm2{T}) X2::Symm2{T} = zero(Symm2{T}) @@ -65,20 +69,26 @@ end jacobian::Symm4{T} = zero(Symm4{T}) end +# TODO: Does this eventually need a {T}? @with_kw struct ChabocheOptions <: AbstractMaterialState nlsolve_method::Symbol = :trust_region end -@with_kw mutable struct Chaboche{T <: Real} <: AbstractMaterial - drivers::ChabocheDriverState{T} = ChabocheDriverState{T}() - ddrivers::ChabocheDriverState{T} = ChabocheDriverState{T}() - variables::ChabocheVariableState{T} = ChabocheVariableState{T}() - variables_new::ChabocheVariableState{T} = ChabocheVariableState{T}() - parameters::ChabocheParameterState{T} = ChabocheParameterState{T}() - dparameters::ChabocheParameterState{T} = ChabocheParameterState{T}() +@with_kw mutable struct GenericChaboche{T <: Real} <: AbstractMaterial + drivers::GenericChabocheDriverState{T} = GenericChabocheDriverState{T}() + ddrivers::GenericChabocheDriverState{T} = GenericChabocheDriverState{T}() + variables::GenericChabocheVariableState{T} = GenericChabocheVariableState{T}() + variables_new::GenericChabocheVariableState{T} = GenericChabocheVariableState{T}() + parameters::GenericChabocheParameterState{T} = GenericChabocheParameterState{T}() + dparameters::GenericChabocheParameterState{T} = GenericChabocheParameterState{T}() options::ChabocheOptions = ChabocheOptions() end +ChabocheDriverState = GenericChabocheDriverState{Float64} +ChabocheParameterState = GenericChabocheParameterState{Float64} +ChabocheVariableState = GenericChabocheVariableState{Float64} +Chaboche = GenericChaboche{Float64} + """ state_to_vector(sigma::U, R::T, X1::U, X2::U) where U <: Symm2{T} where T <: Real @@ -102,11 +112,11 @@ function state_from_vector(x::AbstractVector{T}) where T <: Real end """ - integrate_material!(material::Chaboche{T}) where T <: Real + integrate_material!(material::GenericChaboche{T}) where T <: Real Chaboche material with two backstresses. Both kinematic and isotropic hardening. """ -function integrate_material!(material::Chaboche{T}) where T <: Real +function integrate_material!(material::GenericChaboche{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers @@ -171,7 +181,7 @@ function integrate_material!(material::Chaboche{T}) where T <: Real end """ - create_nonlinear_system_of_equations(material::Chaboche) + create_nonlinear_system_of_equations(material::GenericChaboche{T}) where T <: Real Create and return an instance of the equation system for the incremental form of the evolution equations of the Chaboche material. @@ -194,7 +204,7 @@ X2 are encoded in Voigt format. The function `g!` is intended to be handed over to `nlsolve`. """ -function create_nonlinear_system_of_equations(material::Chaboche{T}) where T <: Real +function create_nonlinear_system_of_equations(material::GenericChaboche{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers diff --git a/src/idealplastic.jl b/src/idealplastic.jl index 681e65a..ea8d0b6 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -9,42 +9,51 @@ import ..AbstractMaterial, ..AbstractMaterialState import ..Utilities: Symm2, Symm4, isotropic_elasticity_tensor, IS, ID, lame import ..integrate_material! # for method extension -export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlaticVariableState +# parametrically polymorphic for any type representing ℝ +export GenericIdealPlastic, GenericIdealPlasticDriverState, GenericIdealPlasticParameterState, GenericIdealPlasticVariableState -@with_kw mutable struct IdealPlasticDriverState{T <: Real} <: AbstractMaterialState +# specialization for Float64 +export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlasticVariableState + +@with_kw mutable struct GenericIdealPlasticDriverState{T <: Real} <: AbstractMaterialState time::T = zero(T) strain::Symm2{T} = zero(Symm2{T}) end -@with_kw struct IdealPlasticParameterState{T <: Real} <: AbstractMaterialState +@with_kw struct GenericIdealPlasticParameterState{T <: Real} <: AbstractMaterialState youngs_modulus::T = zero(T) poissons_ratio::T = zero(T) yield_stress::T = zero(T) end -@with_kw struct IdealPlasticVariableState{T <: Real} <: AbstractMaterialState +@with_kw struct GenericIdealPlasticVariableState{T <: Real} <: AbstractMaterialState stress::Symm2{T} = zero(Symm2{T}) plastic_strain::Symm2{T} = zero(Symm2{T}) cumeq::T = zero(T) jacobian::Symm4{T} = zero(Symm4{T}) end -@with_kw mutable struct IdealPlastic{T <: Real} <: AbstractMaterial - drivers::IdealPlasticDriverState{T} = IdealPlasticDriverState{T}() - ddrivers::IdealPlasticDriverState{T} = IdealPlasticDriverState{T}() - variables::IdealPlasticVariableState{T} = IdealPlasticVariableState{T}() - variables_new::IdealPlasticVariableState{T} = IdealPlasticVariableState{T}() - parameters::IdealPlasticParameterState{T} = IdealPlasticParameterState{T}() - dparameters::IdealPlasticParameterState{T} = IdealPlasticParameterState{T}() +@with_kw mutable struct GenericIdealPlastic{T <: Real} <: AbstractMaterial + drivers::GenericIdealPlasticDriverState{T} = GenericIdealPlasticDriverState{T}() + ddrivers::GenericIdealPlasticDriverState{T} = GenericIdealPlasticDriverState{T}() + variables::GenericIdealPlasticVariableState{T} = GenericIdealPlasticVariableState{T}() + variables_new::GenericIdealPlasticVariableState{T} = GenericIdealPlasticVariableState{T}() + parameters::GenericIdealPlasticParameterState{T} = GenericIdealPlasticParameterState{T}() + dparameters::GenericIdealPlasticParameterState{T} = GenericIdealPlasticParameterState{T}() end +IdealPlastic = GenericIdealPlastic{Float64} +IdealPlasticDriverState = GenericIdealPlasticDriverState{Float64} +IdealPlasticParameterState = GenericIdealPlasticParameterState{Float64} +IdealPlasticVariableState = GenericIdealPlasticVariableState{Float64} + """ - integrate_material!(material::IdealPlastic) + integrate_material!(material::GenericIdealPlastic) Ideal plastic material: no hardening. The elastic region remains centered on the origin, and retains its original size. """ -function integrate_material!(material::IdealPlastic{T}) where T <: Real +function integrate_material!(material::GenericIdealPlastic{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers @@ -83,7 +92,7 @@ function integrate_material!(material::IdealPlastic{T}) where T <: Real # J = ED - (ED : n) ⊗ (n : ED) / (n : ED : n) jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED)) / dcontract(dcontract(n, ED), n) end - variables_new = IdealPlasticVariableState(stress=stress, + variables_new = GenericIdealPlasticVariableState(stress=stress, plastic_strain=plastic_strain, cumeq=cumeq, jacobian=jacobian) diff --git a/test/test_biaxial_increment.jl b/test/test_biaxial_increment.jl index 2cbf563..3d1f10b 100644 --- a/test/test_biaxial_increment.jl +++ b/test/test_biaxial_increment.jl @@ -15,7 +15,7 @@ let dtime = 0.25, D2=1000.0, Q=50.0, b=0.1), - mat = Chaboche{Float64}(parameters = parameters), + mat = Chaboche(parameters = parameters), dstrain11 = 1e-3*dtime, dstrain12 = 1e-3*dtime, dtimes = [dtime, dtime, dtime, dtime, 1.0], diff --git a/test/test_chaboche.jl b/test/test_chaboche.jl index 28ad5a1..5d84548 100644 --- a/test/test_chaboche.jl +++ b/test/test_chaboche.jl @@ -31,7 +31,7 @@ let path = joinpath("test_chaboche", "unitelement_results.rpt"), D2=1000.0, Q=50.0, b=0.1), - mat = Chaboche{Float64}(parameters=parameters) + mat = Chaboche(parameters=parameters) s33s = [mat.variables.stress[3,3]] for i=2:length(ts) diff --git a/test/test_chaboche_shear.jl b/test/test_chaboche_shear.jl index 58765f2..785057d 100644 --- a/test/test_chaboche_shear.jl +++ b/test/test_chaboche_shear.jl @@ -16,7 +16,7 @@ let E = 200.0e3, D2=1000.0, Q=0.0, b=0.1), - mat = Chaboche{Float64}(parameters=parameters), + mat = Chaboche(parameters=parameters), times = [0.0], loads = [0.0], dt = 1.0, diff --git a/test/test_idealplastic.jl b/test/test_idealplastic.jl index 53ee91f..00541ef 100644 --- a/test/test_idealplastic.jl +++ b/test/test_idealplastic.jl @@ -14,7 +14,7 @@ let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, let dtime=0.25 dstrain_dtime = tostrain(epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) - mat = IdealPlastic{Float64}(parameters=parameters, ddrivers=ddrivers) + mat = IdealPlastic(parameters=parameters, ddrivers=ddrivers) integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, uniaxial_stress(50.0)) diff --git a/test/test_idealplastic_shear.jl b/test/test_idealplastic_shear.jl index 0fc2f47..1cbe138 100644 --- a/test/test_idealplastic_shear.jl +++ b/test/test_idealplastic_shear.jl @@ -9,7 +9,7 @@ let E = 200.0e3, parameters = IdealPlasticParameterState(youngs_modulus=E, poissons_ratio=nu, yield_stress=yield_stress), - mat = IdealPlastic{Float64}(parameters=parameters), + mat = IdealPlastic(parameters=parameters), times = [0.0], loads = [0.0], dt = 0.5, diff --git a/test/test_stress_driven_uniaxial_increment.jl b/test/test_stress_driven_uniaxial_increment.jl index 0a4f298..0f5efc1 100644 --- a/test/test_stress_driven_uniaxial_increment.jl +++ b/test/test_stress_driven_uniaxial_increment.jl @@ -15,7 +15,7 @@ let dtime = 0.25, D2=1000.0, Q=0.0, b=0.1), - material = Chaboche{Float64}(parameters=parameters), + material = Chaboche(parameters=parameters), times = [material.drivers.time], stresses = [copy(tovoigt(material.variables.stress))], strains = [copy(tovoigt(material.drivers.strain; offdiagscale=2.0))], diff --git a/test/test_uniaxial_increment.jl b/test/test_uniaxial_increment.jl index 44980ae..fb2f172 100644 --- a/test/test_uniaxial_increment.jl +++ b/test/test_uniaxial_increment.jl @@ -7,7 +7,7 @@ let dtime = 0.25, parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, poissons_ratio=0.3, yield_stress=100.0), - mat = IdealPlastic{Float64}(parameters=parameters), + mat = IdealPlastic(parameters=parameters), tostrain(vec) = fromvoigt(Symm2, vec; offdiagscale=2.0), tostress(vec) = fromvoigt(Symm2, vec), uniaxial_stress(sigma) = tostress([sigma, 0, 0, 0, 0, 0]), From 04e464cd4b74a4cdf4dc63d1ae7a1bf1d3b202f0 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 7 Aug 2020 12:39:55 +0300 Subject: [PATCH 59/66] Rename IdealPlastic -> PerfectPlastic Standard terminology in English. --- examples/abstractmaterial_full_workflow.jl | 22 ++++++------ examples/abstractmaterial_full_workflow2.jl | 22 ++++++------ examples/continuum.jl | 2 +- examples/one_element_ideal_plastic.jl | 2 +- src/Materials.jl | 4 +-- src/idealplastic.jl | 40 ++++++++++----------- test/test_idealplastic.jl | 12 +++---- test/test_idealplastic_shear.jl | 6 ++-- test/test_uniaxial_increment.jl | 4 +-- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/examples/abstractmaterial_full_workflow.jl b/examples/abstractmaterial_full_workflow.jl index 88185d7..0f6f04f 100644 --- a/examples/abstractmaterial_full_workflow.jl +++ b/examples/abstractmaterial_full_workflow.jl @@ -50,18 +50,18 @@ end dparameters :: ChabocheParameterState = ChabocheParameterState() end -@with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState +@with_kw mutable struct PerfectPlasticDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) strain :: Symm2 = zero(Symm2{Float64}) end -@with_kw struct IdealPlasticParameterState <: AbstractMaterialState +@with_kw struct PerfectPlasticParameterState <: AbstractMaterialState youngs_modulus :: Float64 = zero(Float64) poissons_ratio :: Float64 = zero(Float64) yield_stress :: Float64 = zero(Float64) end -@with_kw struct IdealPlasticVariableState <: AbstractMaterialState +@with_kw struct PerfectPlasticVariableState <: AbstractMaterialState stress :: Symm2 = zero(Symm2{Float64}) plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) @@ -92,18 +92,18 @@ end # dparameters :: ParameterState{M} = ParameterState{M}() # end -@with_kw mutable struct IdealPlastic <: AbstractMaterial - drivers :: IdealPlasticDriverState = IdealPlasticDriverState() - ddrivers :: IdealPlasticDriverState = IdealPlasticDriverState() - variables :: IdealPlasticVariableState = IdealPlasticVariableState() - variables_new :: IdealPlasticVariableState = IdealPlasticVariableState() - parameters :: IdealPlasticParameterState = IdealPlasticParameterState() - dparameters :: IdealPlasticParameterState = IdealPlasticParameterState() +@with_kw mutable struct PerfectPlastic <: AbstractMaterial + drivers :: PerfectPlasticDriverState = PerfectPlasticDriverState() + ddrivers :: PerfectPlasticDriverState = PerfectPlasticDriverState() + variables :: PerfectPlasticVariableState = PerfectPlasticVariableState() + variables_new :: PerfectPlasticVariableState = PerfectPlasticVariableState() + parameters :: PerfectPlasticParameterState = PerfectPlasticParameterState() + dparameters :: PerfectPlasticParameterState = PerfectPlasticParameterState() end mat = Chaboche() -mat2 = IdealPlastic() +mat2 = PerfectPlastic() function isotropic_elasticity_tensor(lambda, mu) delta(i,j) = i==j ? 1.0 : 0.0 diff --git a/examples/abstractmaterial_full_workflow2.jl b/examples/abstractmaterial_full_workflow2.jl index e279726..f8991c2 100644 --- a/examples/abstractmaterial_full_workflow2.jl +++ b/examples/abstractmaterial_full_workflow2.jl @@ -53,18 +53,18 @@ end dparameters :: ChabocheParameterState = ChabocheParameterState() end -@with_kw mutable struct IdealPlasticDriverState <: AbstractMaterialState +@with_kw mutable struct PerfectPlasticDriverState <: AbstractMaterialState time :: Float64 = zero(Float64) strain :: Symm2 = zero(Symm2{Float64}) end -@with_kw struct IdealPlasticParameterState <: AbstractMaterialState +@with_kw struct PerfectPlasticParameterState <: AbstractMaterialState youngs_modulus :: Float64 = zero(Float64) poissons_ratio :: Float64 = zero(Float64) yield_stress :: Float64 = zero(Float64) end -@with_kw struct IdealPlasticVariableState <: AbstractMaterialState +@with_kw struct PerfectPlasticVariableState <: AbstractMaterialState stress :: Symm2 = zero(Symm2{Float64}) plastic_strain :: Symm2 = zero(Symm2{Float64}) cumeq :: Float64 = zero(Float64) @@ -95,18 +95,18 @@ end # dparameters :: ParameterState{M} = ParameterState{M}() # end -@with_kw mutable struct IdealPlastic <: AbstractMaterial - drivers :: IdealPlasticDriverState = IdealPlasticDriverState() - ddrivers :: IdealPlasticDriverState = IdealPlasticDriverState() - variables :: IdealPlasticVariableState = IdealPlasticVariableState() - variables_new :: IdealPlasticVariableState = IdealPlasticVariableState() - parameters :: IdealPlasticParameterState = IdealPlasticParameterState() - dparameters :: IdealPlasticParameterState = IdealPlasticParameterState() +@with_kw mutable struct PerfectPlastic <: AbstractMaterial + drivers :: PerfectPlasticDriverState = PerfectPlasticDriverState() + ddrivers :: PerfectPlasticDriverState = PerfectPlasticDriverState() + variables :: PerfectPlasticVariableState = PerfectPlasticVariableState() + variables_new :: PerfectPlasticVariableState = PerfectPlasticVariableState() + parameters :: PerfectPlasticParameterState = PerfectPlasticParameterState() + dparameters :: PerfectPlasticParameterState = PerfectPlasticParameterState() end mat = Chaboche() -mat2 = IdealPlastic() +mat2 = PerfectPlastic() function isotropic_elasticity_tensor(lambda, mu) delta(i,j) = i==j ? 1.0 : 0.0 diff --git a/examples/continuum.jl b/examples/continuum.jl index 499af8f..1e833ae 100644 --- a/examples/continuum.jl +++ b/examples/continuum.jl @@ -5,7 +5,7 @@ mutable struct Continuum3D <: FieldProblem material_model :: Symbol end -Continuum3D() = Continuum3D(:IdealPlastic) +Continuum3D() = Continuum3D(:PerfectPlastic) FEMBase.get_unknown_field_name(::Continuum3D) = "displacement" function FEMBase.assemble_elements!(problem::Problem{Continuum3D}, diff --git a/examples/one_element_ideal_plastic.jl b/examples/one_element_ideal_plastic.jl index 88020a4..d12661d 100644 --- a/examples/one_element_ideal_plastic.jl +++ b/examples/one_element_ideal_plastic.jl @@ -5,7 +5,7 @@ using Materials, FEMBase, LinearAlgebra # Standard simulation of ideal plastic material model -analysis, problem, element, bc_elements, ip = get_material_analysis(:IdealPlastic) +analysis, problem, element, bc_elements, ip = get_material_analysis(:PerfectPlastic) update!(element, "youngs modulus", 200.0e3) update!(element, "poissons ratio", 0.3) update!(element, "yield stress", 100.0) diff --git a/src/Materials.jl b/src/Materials.jl index 77b770b..19818ef 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -80,8 +80,8 @@ export delta, II, IT, IS, IA, IV, ID, isotropic_elasticity_tensor export lame, delame, debang, find_root include("idealplastic.jl") -using .IdealPlasticModule -export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlasticVariableState +using .PerfectPlasticModule +export PerfectPlastic, PerfectPlasticDriverState, PerfectPlasticParameterState, PerfectPlasticVariableState include("chaboche.jl") using .ChabocheModule diff --git a/src/idealplastic.jl b/src/idealplastic.jl index ea8d0b6..0ced933 100644 --- a/src/idealplastic.jl +++ b/src/idealplastic.jl @@ -1,7 +1,7 @@ # This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/Materials.jl/blob/master/LICENSE -module IdealPlasticModule +module PerfectPlasticModule using LinearAlgebra, ForwardDiff, Tensors, NLsolve, Parameters @@ -10,50 +10,50 @@ import ..Utilities: Symm2, Symm4, isotropic_elasticity_tensor, IS, ID, lame import ..integrate_material! # for method extension # parametrically polymorphic for any type representing ℝ -export GenericIdealPlastic, GenericIdealPlasticDriverState, GenericIdealPlasticParameterState, GenericIdealPlasticVariableState +export GenericPerfectPlastic, GenericPerfectPlasticDriverState, GenericPerfectPlasticParameterState, GenericPerfectPlasticVariableState # specialization for Float64 -export IdealPlastic, IdealPlasticDriverState, IdealPlasticParameterState, IdealPlasticVariableState +export PerfectPlastic, PerfectPlasticDriverState, PerfectPlasticParameterState, PerfectPlasticVariableState -@with_kw mutable struct GenericIdealPlasticDriverState{T <: Real} <: AbstractMaterialState +@with_kw mutable struct GenericPerfectPlasticDriverState{T <: Real} <: AbstractMaterialState time::T = zero(T) strain::Symm2{T} = zero(Symm2{T}) end -@with_kw struct GenericIdealPlasticParameterState{T <: Real} <: AbstractMaterialState +@with_kw struct GenericPerfectPlasticParameterState{T <: Real} <: AbstractMaterialState youngs_modulus::T = zero(T) poissons_ratio::T = zero(T) yield_stress::T = zero(T) end -@with_kw struct GenericIdealPlasticVariableState{T <: Real} <: AbstractMaterialState +@with_kw struct GenericPerfectPlasticVariableState{T <: Real} <: AbstractMaterialState stress::Symm2{T} = zero(Symm2{T}) plastic_strain::Symm2{T} = zero(Symm2{T}) cumeq::T = zero(T) jacobian::Symm4{T} = zero(Symm4{T}) end -@with_kw mutable struct GenericIdealPlastic{T <: Real} <: AbstractMaterial - drivers::GenericIdealPlasticDriverState{T} = GenericIdealPlasticDriverState{T}() - ddrivers::GenericIdealPlasticDriverState{T} = GenericIdealPlasticDriverState{T}() - variables::GenericIdealPlasticVariableState{T} = GenericIdealPlasticVariableState{T}() - variables_new::GenericIdealPlasticVariableState{T} = GenericIdealPlasticVariableState{T}() - parameters::GenericIdealPlasticParameterState{T} = GenericIdealPlasticParameterState{T}() - dparameters::GenericIdealPlasticParameterState{T} = GenericIdealPlasticParameterState{T}() +@with_kw mutable struct GenericPerfectPlastic{T <: Real} <: AbstractMaterial + drivers::GenericPerfectPlasticDriverState{T} = GenericPerfectPlasticDriverState{T}() + ddrivers::GenericPerfectPlasticDriverState{T} = GenericPerfectPlasticDriverState{T}() + variables::GenericPerfectPlasticVariableState{T} = GenericPerfectPlasticVariableState{T}() + variables_new::GenericPerfectPlasticVariableState{T} = GenericPerfectPlasticVariableState{T}() + parameters::GenericPerfectPlasticParameterState{T} = GenericPerfectPlasticParameterState{T}() + dparameters::GenericPerfectPlasticParameterState{T} = GenericPerfectPlasticParameterState{T}() end -IdealPlastic = GenericIdealPlastic{Float64} -IdealPlasticDriverState = GenericIdealPlasticDriverState{Float64} -IdealPlasticParameterState = GenericIdealPlasticParameterState{Float64} -IdealPlasticVariableState = GenericIdealPlasticVariableState{Float64} +PerfectPlastic = GenericPerfectPlastic{Float64} +PerfectPlasticDriverState = GenericPerfectPlasticDriverState{Float64} +PerfectPlasticParameterState = GenericPerfectPlasticParameterState{Float64} +PerfectPlasticVariableState = GenericPerfectPlasticVariableState{Float64} """ - integrate_material!(material::GenericIdealPlastic) + integrate_material!(material::GenericPerfectPlastic) Ideal plastic material: no hardening. The elastic region remains centered on the origin, and retains its original size. """ -function integrate_material!(material::GenericIdealPlastic{T}) where T <: Real +function integrate_material!(material::GenericPerfectPlastic{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers @@ -92,7 +92,7 @@ function integrate_material!(material::GenericIdealPlastic{T}) where T <: Real # J = ED - (ED : n) ⊗ (n : ED) / (n : ED : n) jacobian = ED - otimes(dcontract(ED, n), dcontract(n, ED)) / dcontract(dcontract(n, ED), n) end - variables_new = GenericIdealPlasticVariableState(stress=stress, + variables_new = GenericPerfectPlasticVariableState(stress=stress, plastic_strain=plastic_strain, cumeq=cumeq, jacobian=jacobian) diff --git a/test/test_idealplastic.jl b/test/test_idealplastic.jl index 00541ef..1fa8cca 100644 --- a/test/test_idealplastic.jl +++ b/test/test_idealplastic.jl @@ -3,7 +3,7 @@ using Test, Tensors -let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, +let parameters = PerfectPlasticParameterState(youngs_modulus=200.0e3, poissons_ratio=0.3, yield_stress=100.0), epsilon=1e-3, @@ -13,8 +13,8 @@ let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, uniaxial_stress(sigma) = tostress([sigma, 0, 0, 0, 0, 0]) let dtime=0.25 dstrain_dtime = tostrain(epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) - ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) - mat = IdealPlastic(parameters=parameters, ddrivers=ddrivers) + ddrivers = PerfectPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) + mat = PerfectPlastic(parameters=parameters, ddrivers=ddrivers) integrate_material!(mat) update_material!(mat) @test isapprox(mat.variables.stress, uniaxial_stress(50.0)) @@ -26,7 +26,7 @@ let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, @test isapprox(mat.variables.cumeq, 0.0; atol=1.0e-12) dstrain_dtime = tostrain(epsilon*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0]) - ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) + ddrivers = PerfectPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) @@ -34,7 +34,7 @@ let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, @test isapprox(mat.variables.cumeq, dtime*epsilon) dstrain_dtime = tostrain(-epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) - ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) + ddrivers = PerfectPlasticDriverState(time=dtime, strain=dstrain_dtime*dtime) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) @@ -43,7 +43,7 @@ let parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, dstrain_dtime = (-0.75*tostrain(epsilon*[1.0, -0.3, -0.3, 0.0, 0.0, 0.0]) -0.25*tostrain(epsilon*[1.0, -0.5, -0.5, 0.0, 0.0, 0.0])) - ddrivers = IdealPlasticDriverState(time=1.0, strain=dstrain_dtime*1.0) + ddrivers = PerfectPlasticDriverState(time=1.0, strain=dstrain_dtime*1.0) mat.ddrivers = ddrivers integrate_material!(mat) integrate_material!(mat) diff --git a/test/test_idealplastic_shear.jl b/test/test_idealplastic_shear.jl index 1cbe138..1d7f896 100644 --- a/test/test_idealplastic_shear.jl +++ b/test/test_idealplastic_shear.jl @@ -6,10 +6,10 @@ using Test, Tensors let E = 200.0e3, nu = 0.3, yield_stress = 100.0, - parameters = IdealPlasticParameterState(youngs_modulus=E, + parameters = PerfectPlasticParameterState(youngs_modulus=E, poissons_ratio=nu, yield_stress=yield_stress), - mat = IdealPlastic(parameters=parameters), + mat = PerfectPlastic(parameters=parameters), times = [0.0], loads = [0.0], dt = 0.5, @@ -36,7 +36,7 @@ let E = 200.0e3, dstrain31 = loads[i] - loads[i-1] dstrain = [0.0, 0.0, 0.0, 0.0, 0.0, dstrain31] dstrain_ = fromvoigt(Symm2{Float64}, dstrain; offdiagscale=2.0) - ddrivers = IdealPlasticDriverState(time=dtime, strain=dstrain_) + ddrivers = PerfectPlasticDriverState(time=dtime, strain=dstrain_) mat.ddrivers = ddrivers integrate_material!(mat) update_material!(mat) diff --git a/test/test_uniaxial_increment.jl b/test/test_uniaxial_increment.jl index fb2f172..c8012b5 100644 --- a/test/test_uniaxial_increment.jl +++ b/test/test_uniaxial_increment.jl @@ -4,10 +4,10 @@ using Test, Tensors let dtime = 0.25, - parameters = IdealPlasticParameterState(youngs_modulus=200.0e3, + parameters = PerfectPlasticParameterState(youngs_modulus=200.0e3, poissons_ratio=0.3, yield_stress=100.0), - mat = IdealPlastic(parameters=parameters), + mat = PerfectPlastic(parameters=parameters), tostrain(vec) = fromvoigt(Symm2, vec; offdiagscale=2.0), tostress(vec) = fromvoigt(Symm2, vec), uniaxial_stress(sigma) = tostress([sigma, 0, 0, 0, 0, 0]), From fb6c44718d4a52ba602c0202f9194f09d51a9285 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 7 Aug 2020 12:42:52 +0300 Subject: [PATCH 60/66] Rename the affected files --- examples/one_element_ideal_plastic.jl | 2 +- src/Materials.jl | 2 +- src/{idealplastic.jl => perfectplastic.jl} | 0 test/runtests.jl | 8 ++++---- test/{test_idealplastic.jl => test_perfectplastic.jl} | 0 ...idealplastic_shear.jl => test_perfectplastic_shear.jl} | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename src/{idealplastic.jl => perfectplastic.jl} (100%) rename test/{test_idealplastic.jl => test_perfectplastic.jl} (100%) rename test/{test_idealplastic_shear.jl => test_perfectplastic_shear.jl} (100%) diff --git a/examples/one_element_ideal_plastic.jl b/examples/one_element_ideal_plastic.jl index d12661d..15fd1cb 100644 --- a/examples/one_element_ideal_plastic.jl +++ b/examples/one_element_ideal_plastic.jl @@ -44,7 +44,7 @@ t = 0.0:0.1:3.0 plot(e11.(t), s11.(t), label="\$\\sigma_{11}\$") plot!(e22.(t), s22.(t), label="\$\\sigma_{22}\$") plot!(e33.(t), s33.(t), label="\$\\sigma_{33}\$") -title!("Stress-strain curve of idealplastic material model, uniaxial strain") +title!("Stress-strain curve of perfect plastic material model, uniaxial strain") ylabel!("Stress [MPa]") xlabel!("Strain [str]") savefig(joinpath("one_element_ideal_plastic/uniaxial_strain.svg")) diff --git a/src/Materials.jl b/src/Materials.jl index 19818ef..8c6f67c 100644 --- a/src/Materials.jl +++ b/src/Materials.jl @@ -79,7 +79,7 @@ export Symm2, Symm4 export delta, II, IT, IS, IA, IV, ID, isotropic_elasticity_tensor export lame, delame, debang, find_root -include("idealplastic.jl") +include("perfectplastic.jl") using .PerfectPlasticModule export PerfectPlastic, PerfectPlasticDriverState, PerfectPlasticParameterState, PerfectPlasticVariableState diff --git a/src/idealplastic.jl b/src/perfectplastic.jl similarity index 100% rename from src/idealplastic.jl rename to src/perfectplastic.jl diff --git a/test/runtests.jl b/test/runtests.jl index 32211fe..d99501d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,11 +7,11 @@ using Materials, Test @testset "test utilities" begin include("test_utilities.jl") end - @testset "test ideal plastic uniaxial stress" begin - include("test_idealplastic.jl") + @testset "test perfect plastic uniaxial stress" begin + include("test_perfectplastic.jl") end - @testset "test ideal plastic pure shear" begin - include("test_idealplastic_shear.jl") + @testset "test perfect plastic pure shear" begin + include("test_perfectplastic_shear.jl") end @testset "test chaboche uniaxial stress" begin include("test_chaboche.jl") diff --git a/test/test_idealplastic.jl b/test/test_perfectplastic.jl similarity index 100% rename from test/test_idealplastic.jl rename to test/test_perfectplastic.jl diff --git a/test/test_idealplastic_shear.jl b/test/test_perfectplastic_shear.jl similarity index 100% rename from test/test_idealplastic_shear.jl rename to test/test_perfectplastic_shear.jl From 733432241f2d9e9f900ee10a7280692c4055588f Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 7 Aug 2020 12:53:19 +0300 Subject: [PATCH 61/66] optimize_dstrain! -> find_dstrain!; relevant docstring updates --- src/increments.jl | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/increments.jl b/src/increments.jl index c4b63ef..487ff69 100644 --- a/src/increments.jl +++ b/src/increments.jl @@ -9,18 +9,25 @@ import Tensors: tovoigt, fromvoigt import ..AbstractMaterial, ..integrate_material! import ..Utilities: Symm2 -export optimize_dstrain!, uniaxial_increment!, biaxial_increment!, stress_driven_uniaxial_increment! +export find_dstrain!, uniaxial_increment!, biaxial_increment!, stress_driven_uniaxial_increment! # The skeleton of the optimizer is always the same, so we provide it as a # higher-order function. The individual specific optimizer functions # (`update_dstrain!)` only need to define the "meat" of how to update `dstrain`. """ - optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, - dt::Real, update_dstrain!::Function; - max_iter::Integer=50, tol::Real=1e-9) + find_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, + dt::Real, update_dstrain!::Function; + max_iter::Integer=50, tol::Real=1e-9) Find a compatible strain increment for `material`. +The functions in this module are made to be able to easily simulate stress +states produced by some of the most common test machines. Take for example the +function `uniaxial_increment!`. In a push-pull machine (with a smooth specimen), +we know that the stress state is uniaxial (in the measuring volume), so given +the strain increment in the direction where the stress is nonzero, we find +a strain increment that produces zero stress in the other directions. + The `dstrain` supplied to this routine is the initial guess for the optimization. At each iteration, it must be updated by the user-defined corrector `update_dstrain!`, whose call signature is expected to be: @@ -38,8 +45,10 @@ integrating the material for one timestep of length `dt`, using the current value of `dstrain` as a driving strain increment, and `stress0` is the stress state stored in `materials.variables.stress`. -`jacobian` is ∂σij/∂εkl (`material.variables_new.jacobian`), provided by the -material implementation. +`jacobian` is ∂σij/∂εkl (`material.variables_new.jacobian`), as computed by the +material implementation. In many cases, the dstrain optimization can actually be +performed by a Newton-Raphson root finder, so we pass the jacobian to facilitate +writing the update formula for such a root finder. The return value `err` must be an error measure (Real, >= 0). @@ -51,9 +60,9 @@ If `max_iter` is reached and the error measure is still `tol` or greater, Note the timestep is **not** committed; we call `integrate_material!`, but not `update_material!`. Only `material.variables_new` is updated. """ -function optimize_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, - dt::Real, update_dstrain!::Function; - max_iter::Integer=50, tol::Real=1e-9) +function find_dstrain!(material::AbstractMaterial, dstrain::AbstractVector{<:Real}, + dt::Real, update_dstrain!::Function; + max_iter::Integer=50, tol::Real=1e-9) stress0 = tovoigt(material.variables.stress) # observed T = typeof(dstrain[1]) # @debug "---START---" @@ -85,7 +94,7 @@ increment are taken as prescribed. This routine computes the other components of the strain increment such that the predicted stress state matches the stored one. -See `optimize_dstrain!`. +See `find_dstrain!`. """ function uniaxial_increment!(material::AbstractMaterial, dstrain11::Real, dt::Real; dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0.0, 0.0, 0.0], @@ -95,7 +104,7 @@ function uniaxial_increment!(material::AbstractMaterial, dstrain11::Real, dt::Re dstrain[2:end] .+= dstrain_correction return norm(dstrain_correction) end - optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) + find_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end @@ -111,7 +120,7 @@ The material state (`material.variables`) and the components 11 and 12 of the components of the strain increment such that the predicted stress state matches the stored one. -See `optimize_dstrain!`. +See `find_dstrain!`. """ function biaxial_increment!(material::AbstractMaterial, dstrain11::Real, dstrain12::Real, dt::Real; dstrain::AbstractVector{<:Real}=[dstrain11, -0.3*dstrain11, -0.3*dstrain11, 0, 0, dstrain12], @@ -121,7 +130,7 @@ function biaxial_increment!(material::AbstractMaterial, dstrain11::Real, dstrain dstrain[2:end-1] .+= dstrain_correction return norm(dstrain_correction) end - optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) + find_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end @@ -136,7 +145,7 @@ The material state (`material.variables`) and the component 11 of the *stress* increment are taken as prescribed. This routine computes a strain increment such that the predicted stress state matches the stored one. -See `optimize_dstrain!`. +See `find_dstrain!`. """ function stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11::Real, dt::Real; dstrain::AbstractVector{<:Real}=[dstress11/200e3, -0.3*dstress11/200e3, -0.3*dstress11/200e3, 0.0, 0.0, 0.0], @@ -149,7 +158,7 @@ function stress_driven_uniaxial_increment!(material::AbstractMaterial, dstress11 dstrain .+= dstrain_correction return norm(dstrain_correction) end - optimize_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) + find_dstrain!(material, dstrain, dt, update_dstrain!, max_iter=max_iter, tol=norm_acc) return nothing end From f6883493d95cf448809ac2b0901b176ec04e2169 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 7 Aug 2020 12:54:43 +0300 Subject: [PATCH 62/66] Add clarifying Wikipedia link for representations of elastic moduli. --- src/utilities.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utilities.jl b/src/utilities.jl index fa1a22c..83efd36 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -83,6 +83,9 @@ isotropic_elasticity_tensor(lambda::T, mu::T) where T <: Real = 3 * lambda * IV( lame(E::Real, nu::Real) Convert elastic parameters (E, nu) of an isotropic material to Lamé parameters (lambda, mu). + +See: + https://en.wikipedia.org/wiki/Template:Elastic_moduli """ function lame(E::Real, nu::Real) lambda = E * nu / ((1 + nu) * (1 - 2 * nu)) @@ -94,6 +97,9 @@ end delame(lambda::Real, mu::Real) Convert Lamé parameters (lambda, mu) of an isotropic material to elastic parameters (E, nu). + +See: + https://en.wikipedia.org/wiki/Template:Elastic_moduli """ function delame(lambda::Real, mu::Real) E = mu * (3 * lambda + 2 * mu) / (lambda + mu) From 011041d7a7683f9ee8bac1262b038a845f6ab6f5 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Fri, 7 Aug 2020 14:08:38 +0300 Subject: [PATCH 63/66] Meh, fix indentation. prettify-symbols-mode for Emacs is nice, but sometimes things like this happen... --- src/utilities.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 83efd36..a302ea4 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -171,8 +171,8 @@ automatically. In this case the output of `f` must be an `AbstractArray`. iterations. """ function find_root(f::Function, x::AbstractVector{<:Real}, - dfdx::Union{Function, Nothing}=nothing; - max_iter::Integer=50, tol::Real=1e-9) + dfdx::Union{Function, Nothing}=nothing; + max_iter::Integer=50, tol::Real=1e-9) if dfdx === nothing dfdx = (x) -> ForwardDiff.jacobian(f, x) end From 7d1d623ab421cb9366a248dfc9075c9201f3560d Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 10 Aug 2020 13:31:21 +0300 Subject: [PATCH 64/66] Paragraph breaks for readability --- src/chaboche.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chaboche.jl b/src/chaboche.jl index 8383046..d837252 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -254,7 +254,9 @@ function create_nonlinear_system_of_equations(material::GenericChaboche{T}) wher dstrain_plastic = dp*n dstrain_elastic = dstrain - dstrain_plastic tovoigt!(view(F, 1:6), stress_new - stress - dcontract(jacobian, dstrain_elastic)) + F[7] = R_new - R - b*(Q - R_new)*dp + tovoigt!(view(F, 8:13), X1_new - X1 - dp*(2.0/3.0*C1*n - D1*X1_new)) tovoigt!(view(F, 14:19), X2_new - X2 - dp*(2.0/3.0*C2*n - D2*X2_new)) return nothing From 3a64a70c9328ffe99ae6894e52c7957247396f2e Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 10 Aug 2020 13:31:35 +0300 Subject: [PATCH 65/66] Oops, use correct data type --- src/chaboche.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/chaboche.jl b/src/chaboche.jl index d837252..8136550 100644 --- a/src/chaboche.jl +++ b/src/chaboche.jl @@ -169,13 +169,13 @@ function integrate_material!(material::GenericChaboche{T}) where T <: Real drde[1:6, 1:6] = tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) end - variables_new = ChabocheVariableState(stress = stress, - X1 = X1, - X2 = X2, - R = R, - plastic_strain = plastic_strain, - cumeq = cumeq, - jacobian = jacobian) + variables_new = GenericChabocheVariableState{T}(stress = stress, + X1 = X1, + X2 = X2, + R = R, + plastic_strain = plastic_strain, + cumeq = cumeq, + jacobian = jacobian) material.variables_new = variables_new return nothing end From 3f7e915197c0c07a9b775353ec6023a892380716 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 10 Aug 2020 14:44:12 +0300 Subject: [PATCH 66/66] Refactor DSA implementation --- src/DSA.jl | 354 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 237 insertions(+), 117 deletions(-) diff --git a/src/DSA.jl b/src/DSA.jl index d89a144..b38bb66 100644 --- a/src/DSA.jl +++ b/src/DSA.jl @@ -3,10 +3,6 @@ # TODO: write docstrings for all public functions # TODO: add comments where needed -# TODO: generalize to generic Real {T} -# TODO: add abstraction for problem state marshaling (see chaboche.jl) -# TODO: use debang -# TODO: use clearer names for variables module DSAModule @@ -16,175 +12,299 @@ import ..AbstractMaterial, ..AbstractMaterialState import ..Utilities: Symm2, Symm4, isotropic_elasticity_tensor, lame, debang import ..integrate_material! # for method extension +# parametrically polymorphic for any type representing ℝ +export GenericDSA, GenericDSADriverState, GenericDSAParameterState, GenericDSAVariableState + +# specialization for Float64 export DSA, DSADriverState, DSAParameterState, DSAVariableState -@with_kw mutable struct DSADriverState <: AbstractMaterialState - time::Float64 = zero(Float64) - strain::Symm2{Float64} = zero(Symm2{Float64}) +@with_kw mutable struct GenericDSADriverState{T <: Real} <: AbstractMaterialState + time::T = zero(T) + strain::Symm2{T} = zero(Symm2{T}) +end + +# TODO: complete this docstring +"""Parameter state for DSA (dynamic strain aging) material. + +This is similar to the Chaboche model, but with additional static recovery terms. + +`E`: Young's modulus +`nu`: Poisson's ratio +`R0`: initial yield strength +`Kn`: plasticity multiplier divisor (drag stress) +`nn`: plasticity multiplier exponent +`C1`, `D1`: parameters governing behavior of backstress X1 +`C2`, `D2`: parameters governing behavior of backstress X2 +`Q`: shift parameter for yield strength evolution +`b`: multiplier for yield strength evolution +`w`: ??? +`P1`, `P2`: ??? +`m`: ??? +`m1`, `m2`: ??? +`M1`, `M2`: ??? +`ba`: ??? +`xi`: ??? +""" +@with_kw struct GenericDSAParameterState{T <: Real} <: AbstractMaterialState + E::T = 0.0 + nu::T = 0.0 + R0::T = 0.0 + Kn::T = 0.0 + nn::T = 0.0 + C1::T = 0.0 + D1::T = 0.0 + C2::T = 0.0 + D2::T = 0.0 + Q::T = 0.0 + b::T = 0.0 + w::T = 0.0 + P1::T = 0.0 + P2::T = 0.0 + m::T = 0.0 + m1::T = 0.0 + m2::T = 0.0 + M1::T = 0.0 + M2::T = 0.0 + ba::T = 0.0 + xi::T = 0.0 end -@with_kw struct DSAParameterState <: AbstractMaterialState - E::Float64 = 0.0 - nu::Float64 = 0.0 - R0::Float64 = 0.0 - Kn::Float64 = 0.0 - nn::Float64 = 0.0 - C1::Float64 = 0.0 - D1::Float64 = 0.0 - C2::Float64 = 0.0 - D2::Float64 = 0.0 - Q::Float64 = 0.0 - b::Float64 = 0.0 - w::Float64 = 0.0 - P1::Float64 = 0.0 - P2::Float64 = 0.0 - m::Float64 = 0.0 - m1::Float64 = 0.0 - m2::Float64 = 0.0 - M1::Float64 = 0.0 - M2::Float64 = 0.0 - ba::Float64 = 0.0 - xi::Float64 = 0.0 +# TODO: complete this docstring +"""Problem state for Chaboche material. + +`stress`: stress tensor +`X1`: backstress 1 +`X2`: backstress 2 +`plastic_strain`: plastic part of strain tensor +`cumeq`: cumulative equivalent plastic strain (scalar, ≥ 0) +`R`: yield strength +`jacobian`: ∂σij/∂εkl +`ta`: ??? +`Ra`: ??? +""" +@with_kw struct GenericDSAVariableState{T <: Real} <: AbstractMaterialState + stress::Symm2{T} = zero(Symm2{T}) + X1::Symm2{T} = zero(Symm2{T}) + X2::Symm2{T} = zero(Symm2{T}) + plastic_strain::Symm2{T} = zero(Symm2{T}) + cumeq::T = zero(T) + R::T = zero(T) + jacobian::Symm4{T} = zero(Symm4{T}) + ta::T = zero(T) + Ra::T = zero(T) +end + +# TODO: Does this eventually need a {T}? +@with_kw struct DSAOptions <: AbstractMaterialState + nlsolve_method::Symbol = :trust_region end -@with_kw struct DSAVariableState <: AbstractMaterialState - stress::Symm2{Float64} = zero(Symm2{Float64}) - X1::Symm2{Float64} = zero(Symm2{Float64}) - X2::Symm2{Float64} = zero(Symm2{Float64}) - plastic_strain::Symm2{Float64} = zero(Symm2{Float64}) - cumeq::Float64 = zero(Float64) - R::Float64 = zero(Float64) - jacobian::Symm4{Float64} = zero(Symm4{Float64}) - ta::Float64 = zero(Float64) - Ra::Float64 = zero(Float64) +@with_kw mutable struct GenericDSA{T <: Real} <: AbstractMaterial + drivers::GenericDSADriverState{T} = GenericDSADriverState{T}() + ddrivers::GenericDSADriverState{T} = GenericDSADriverState{T}() + variables::GenericDSAVariableState{T} = GenericDSAVariableState{T}() + variables_new::GenericDSAVariableState{T} = GenericDSAVariableState{T}() + parameters::GenericDSAParameterState{T} = GenericDSAParameterState{T}() + dparameters::GenericDSAParameterState{T} = GenericDSAParameterState{T}() + options::DSAOptions = DSAOptions() end -@with_kw mutable struct DSA <: AbstractMaterial - drivers::DSADriverState = DSADriverState() - ddrivers::DSADriverState = DSADriverState() - variables::DSAVariableState = DSAVariableState() - variables_new::DSAVariableState = DSAVariableState() - parameters::DSAParameterState = DSAParameterState() - dparameters::DSAParameterState = DSAParameterState() +DSADriverState = GenericDSADriverState{Float64} +DSAParameterState = GenericDSAParameterState{Float64} +DSAVariableState = GenericDSAVariableState{Float64} +DSA = GenericDSA{Float64} + +""" + state_to_vector(sigma::U, R::T, X1::U, X2::U, ta::T, Ra::T) where U <: Symm2{T} where T <: Real + +Adaptor for `nlsolve`. Marshal the problem state into a `Vector`. +""" +function state_to_vector(sigma::U, R::T, X1::U, X2::U, ta::T, Ra::T) where U <: Symm2{T} where T <: Real + return [tovoigt(sigma); R; tovoigt(X1); tovoigt(X2); ta; Ra]::Vector{T} end -function integrate_material!(material::DSA) +""" + state_from_vector(x::AbstractVector{<:Real}) + +Adaptor for `nlsolve`. Unmarshal the problem state from a `Vector`. +""" +function state_from_vector(x::AbstractVector{T}) where T <: Real + sigma::Symm2{T} = fromvoigt(Symm2{T}, @view x[1:6]) + R::T = x[7] + X1::Symm2{T} = fromvoigt(Symm2{T}, @view x[8:13]) + X2::Symm2{T} = fromvoigt(Symm2{T}, @view x[14:19]) + ta::T = x[20] + Ra::T = x[21] + return sigma, R, X1, X2, ta, Ra +end + +""" + integrate_material!(material::GenericDSA{T}) where T <: Real + +Material model with dynamic strain aging (DSA). + +This is similar to the Chaboche material with two backstresses, with both +kinematic and isotropic hardening, but this model also features static recovery +terms. +""" +function integrate_material!(material::GenericDSA{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers d = material.drivers @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b, w, P1, P2, m, m1, m2, M1, M2, ba, xi = p lambda, mu = lame(E, nu) + @unpack strain, time = d dstrain = dd.strain dtime = dd.time @unpack stress, X1, X2, plastic_strain, cumeq, R, jacobian, ta, Ra = v + # elastic part jacobian = isotropic_elasticity_tensor(lambda, mu) stress += dcontract(jacobian, dstrain) - seff = stress - X1 - X2 - seff_dev = dev(seff) - f = sqrt(1.5) * norm(seff_dev) - (R0 + R + (1 - xi) * Ra) + # resulting deviatoric plastic stress (accounting for backstresses Xm) + seff_dev = dev(stress - X1 - X2) + # von Mises yield function + f = sqrt(1.5)*norm(seff_dev) - (R0 + R + (1 - xi) * Ra) # using elastic trial problem state if f > 0.0 g! = create_nonlinear_system_of_equations(material) - x0 = [tovoigt(stress); R; tovoigt(X1); tovoigt(X2); ta + dtime; Ra] - F = similar(x0) - res = nlsolve(g!, x0; autodiff = :forward) + x0 = state_to_vector(stress, R, X1, X2, ta + dtime, Ra) + res = nlsolve(g!, x0; method=material.options.nlsolve_method, autodiff = :forward) + converged(res) || error("Nonlinear system of equations did not converge!") x = res.zero - res.f_converged || error("Nonlinear system of equations did not converge!") - - stress = fromvoigt(Symm2{Float64}, @view x[1:6]) - R = x[7] - X1 = fromvoigt(Symm2{Float64}, @view x[8:13]) - X2 = fromvoigt(Symm2{Float64}, @view x[14:19]) - ta = x[20] - Ra = x[21] - - seff = stress - X1 - X2 - seff_dev = dev(seff) - f = sqrt(1.5) * norm(seff_dev) - ( R0 + R + (1 - xi) * Ra ) - dotp = ( (f >= 0.0 ? f : 0.0) / (Kn + xi * Ra) )^nn - dp = dotp * dtime - n = sqrt(1.5) * seff_dev / norm(seff_dev) - plastic_strain += dp * n + stress, R, X1, X2, ta, Ra = state_from_vector(x) + + # using the new problem state + seff_dev = dev(stress - X1 - X2) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R + (1 - xi) * Ra) + + dotp = ((f >= 0.0 ? f : 0.0) / (Kn + xi * Ra))^nn + dp = dotp*dtime + n = sqrt(1.5)*seff_dev/norm(seff_dev) + + plastic_strain += dp*n cumeq += dp - function residuals(x) # Compute Jacobian - F = similar(x) - g!(F, x) - return F - end - drdx = ForwardDiff.jacobian(residuals, x) + # Compute the new Jacobian, accounting for the plastic contribution. + drdx = ForwardDiff.jacobian(debang(g!), x) drde = zeros((length(x), 6)) - drde[1:6, 1:6] = -tovoigt(jacobian) - jacobian = fromvoigt(Symm4{Float64}, (drdx \ drde)[1:6, 1:6]) + drde[1:6, 1:6] = -tovoigt(jacobian) # elastic Jacobian. Follows from the defn. of g!. + jacobian = fromvoigt(Symm4, (drdx\drde)[1:6, 1:6]) else ta += dtime - end - variables_new = DSAVariableState(stress = stress, X1 = X1, X2 = X2, R = R, plastic_strain = plastic_strain, cumeq = cumeq, jacobian = jacobian, ta = ta, Ra = Ra) + end + variables_new = GenericDSAVariableState{T}(stress = stress, + X1 = X1, + X2 = X2, + R = R, + plastic_strain = plastic_strain, + cumeq = cumeq, + jacobian = jacobian, + ta = ta, + Ra = Ra) material.variables_new = variables_new + return nothing end -function create_nonlinear_system_of_equations(material::DSA) +""" + create_nonlinear_system_of_equations(material::GenericDSA{T}) where T <: Real + +Create and return an instance of the equation system for the incremental form of +the evolution equations of the DSA material. + +Used internally for computing the plastic contribution in `integrate_material!`. + +The input `material` represents the problem state at the end of the previous +timestep. The created equation system will hold its own copy of that state. + +The equation system is represented as a mutating function `g!` that computes the +residual: + +```julia + g!(F::V, x::V) where V <: AbstractVector{<:Real} +``` + +Both `F` (output) and `x` (input) are length-21 vectors containing +[sigma, R, X1, X2, ta, Ra], in that order. The tensor quantities +sigma, X1, X2 are encoded in Voigt format. + +The function `g!` is intended to be handed over to `nlsolve`. +""" +function create_nonlinear_system_of_equations(material::GenericDSA{T}) where T <: Real p = material.parameters v = material.variables dd = material.ddrivers d = material.drivers @unpack E, nu, R0, Kn, nn, C1, D1, C2, D2, Q, b, w, P1, P2, m, m1, m2, M1, M2, ba, xi = p lambda, mu = lame(E, nu) + + # Old problem state (i.e. the problem state at the time when this equation + # system instance was created). + # + # Note this does not include the elastic trial; this is the state at the + # end of the previous timestep. @unpack strain, time = d dstrain = dd.strain dtime = dd.time @unpack stress, X1, X2, plastic_strain, cumeq, R, ta, Ra = v - function g!(F, x::Vector{T}) where {T} # System of non-linear equations - stress_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[1:6]) - R_ = x[7] - X1_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[8:13]) - X2_ = fromvoigt(SymmetricTensor{2,3,T}, @view x[14:19]) - ta_ = x[20] - Ra_ = x[21] - - jacobian = isotropic_elasticity_tensor(lambda, mu) - seff = stress_ - X1_ - X2_ - seff_dev = dev(seff) - f = sqrt(1.5) * norm(seff_dev) - ( R0 + R_ + (1 - xi) * Ra_ ) - dotp = ( (f >= 0.0 ? f : 0.0) / (Kn + xi * Ra_) )^nn - dp = dotp * dtime - n = sqrt(1.5) * seff_dev / norm(seff_dev) - Ras = P1 * (1.0 - exp(-P2 * ta_^m)) - dstrain_plastic = dp * n - - tovoigt!(view(F, 1:6), stress - stress_ + dcontract(jacobian, dstrain - dstrain_plastic)) - F[7] = R - R_ + b * ( Q - R_ ) * dp - # TODO: fix unnecessary division by C1 (see chaboche.jl) + jacobian = isotropic_elasticity_tensor(lambda, mu) + + # Compute the residual. F is output, x is filled by NLsolve. + # The solution is x = x* such that g(x*) = 0. + function g!(F::V, x::V) where V <: AbstractVector{<:Real} + stress_new, R_new, X1_new, X2_new, ta_new, Ra_new = state_from_vector(x) # tentative new values from nlsolve + + seff_dev = dev(stress_new - X1_new - X2_new) + f = sqrt(1.5)*norm(seff_dev) - (R0 + R_new + (1 - xi) * Ra_new) + + dotp = ((f >= 0.0 ? f : 0.0) / (Kn + xi * Ra_new))^nn + dp = dotp*dtime + n = sqrt(1.5)*seff_dev/norm(seff_dev) + + # The equations are written in an incremental form. + # TODO: multiply the equations by -1 to make them easier to understand in the context of the rest of the model. + + dstrain_plastic = dp*n + dstrain_elastic = dstrain - dstrain_plastic + tovoigt!(view(F, 1:6), stress - stress_new + dcontract(jacobian, dstrain_elastic)) + + F[7] = R - R_new + b*(Q - R_new)*dp + + # TODO: figure out what's going on here. This doesn't reduce consistently at the limit C1 -> 0. if isapprox(C1, 0.0) - tovoigt!(view(F, 8:13), X1 - X1_) + tovoigt!(view(F, 8:13), X1 - X1_new) else - ndX1_ = norm(dev(X1_)) # Static recovery component - if iszero(ndX1_) - JX1_ = 0.0 + ndX1_new = norm(dev(X1_new)) # Static recovery component + if iszero(ndX1_new) + JX1_new = 0.0 else - JX1_ = sqrt(1.5) * ndX1_ + JX1_new = sqrt(1.5) * ndX1_new end - sr1_ = ( JX1_^(m1 - 1) * X1_ ) / (M1^m1) - tovoigt!(view(F, 8:13), X1 - X1_ + 2.0 / 3.0 * C1 * dp * ( n - 1.5 * D1 / C1 * X1_ ) - dtime * sr1_) + # JX1_new = sqrt(1.5)*norm(dev(X1_new)) + sr1_new = (JX1_new^(m1 - 1) * X1_new) / (M1^m1) # static recovery + tovoigt!(view(F, 8:13), X1 - X1_new + dp*(2.0/3.0*C1*n - D1*X1_new) - dtime*sr1_new) end if isapprox(C2, 0.0) - tovoigt!(view(F, 14:19), X2 - X2_) + tovoigt!(view(F, 14:19), X2 - X2_new) else - ndX2_ = norm(dev(X2_)) # Static recovery component - if iszero(ndX2_) - JX2_ = 0.0 + ndX2_new = norm(dev(X2_new)) # Static recovery component + if iszero(ndX2_new) + JX2_new = 0.0 else - JX2_ = sqrt(1.5) * ndX2_ + JX2_new = sqrt(1.5) * ndX2_new end - sr2_ = ( JX2_^(m2 - 1) * X2_ ) / (M2^m2) - tovoigt!(view(F, 14:19), X2 - X2_ + 2.0 / 3.0 * C2 * dp * ( n - 1.5 * D2 / C2 * X2_ ) - dtime * sr2_) + # JX2_new = sqrt(1.5)*norm(dev(X2_new)) + sr2_new = (JX2_new^(m2 - 1) * X2_new) / (M2^m2) # static recovery + tovoigt!(view(F, 14:19), X2 - X2_new + dp*(2.0/3.0*C2*n - D2*X2_new) - dtime*sr2_new) end - F[20] = ta - ta_ + dtime - (ta_ / w) * dp - F[21] = Ra - Ra_ + ba * (Ras - Ra_) * dp + + Ras = P1 * (1.0 - exp(-P2 * ta_new^m)) + F[20] = ta - ta_new + dtime - (ta_new / w)*dp + F[21] = Ra - Ra_new + ba*(Ras - Ra_new)*dp + return nothing end return g! end