-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Historic variables in Gridap #155
Comments
As far as I understand, How can we include this update of the One can think about creating some kind of clausure but not sure how to trigger the update. One could consider that the unknowns are By the way, it is mandatory in nonlinear solid mechanics to provide the value of We can discuss alternatives tomorrow |
Could you summarize the equations to be solved? perhaps a scanned note? |
Essentially, we need to decide which are the unknowns of the problem, and define the "algebraic" non-linear operator associated with it. I.e., the operator that given a vector for unknowns (possibly having historic variables as well), it provides the corresponding residual (a vector) and jacobian (a sparse matrix). For FE computations, we were able (up to now) to hide this "algebraic" operator inside a "FEOperator". Perhaps for the case of historic variables, we need different way of constructing the "algebraic" operator. |
By seeing the notes, it is perhaps better to discuss this face to face... |
Yes, one option is to define the problem in terms of the unknowns In that case, my doubt about how to express the law vanishes and the problem is how to efficiently express the problem
|
Ok, but the notes are not that important, the point is how to express the problem... the damage model is just an example. We agree that it is better to talk tomorrow face-to-face. |
The situation is even more complicated, the expression of the stress tensor and the constitutive tensor do not have a closed form in terms of the historic and state variables but they are also Gauss point dependent depending on the situation (e.g., elastic or inelastic regime). I have tried to express the idea in the following pseudo-code. We can start working on this idea. const E = 2.1e4
const ν = 0.3
const λ = (E*ν)/((1+ν)*(1-2*ν))
const μ = E/(2*(1+ν))
# Damage model input
const σ_u = 5.5
const r_0 = σ_u / sqrt(E)
const H = 0.1
q(r) = r_0 + H*(r-r_0)
# Here we should create FEFunctions for the historic variables r and d
@law σ(x,ε) = λ*tr(ε)*one(ε) + 2*μ*ε
@law τ(x,ε) = sqrt(inner(ε(u),σ(x,ε)))
function residual_and_jacobian_at_gauss_point(x_old,x_current,y,dx) # evaluated at a Gauss point
du = x[1]
v = y[1]
u, r_current, d_current = x_current
tmp, r_old, d_old = x_old
τ_current = τ(x,ε(u))
if (τ_current <= r_old) # elastic regime
r_current = r_old # update r at this integration point
d_current = d_old
@law σ_new(x,ε) = (1-d_current)*σ(x,ε)
@law dσ_new(x,dε,ε) = (1-d_current)*σ(x,dε)
else # inelastic regime
r_current = τ_current
d_current = 1 - q(r_current)/r_current
@law σ_new(x,ε) = (1-d_current)*σ(x,ε)
@law dσ_new(x,dε,ε) = (1-d_current)*σ(x,dε) - ((q(r_current)- H*r_current)/(r_current^3))*inner(σ(x,ε),dε)*σ(x,ε)
end
x_current = tmp, r_current, d_current
res = inner( ε(v), σ_new(ε(u)) )
jac = inner( ε(v), dσ_new(ε(du),ε(u)) )
return x_current, res, jac
end
##
for load_stages
u_old, r_old, d_old = x_old # Values from previous time step, i.e., initial state
x_current = x_old # Start nonlinear iterations with previous time step values
for nonlinear iterations
nonlinear_solid_mechanics!(x_current,x_old)
# using the previous Gauss point-based expression of the forms and updating inside the
# historic variables
end
x_old = x_current
end
##
|
Let us assume that we want to solve a mechanical problem with a body force, - div(sigma) = f. At a generic point (e.g., a gauss point), the relation between the symmetric gradient of the displacement and the corresponding stress is given by a julia function with the following signature σ_gp, state_gp_new = constitutive_relation_gp(ε_gp, state_gp_old) Note that this relation is written in terms of a state variable, which is updated from In addition, we have a linearization of this constitutive relation in a julia function with the following signature σ_gp, dσ_gp, state_gp_new = constitutive_relation_gp(ε_gp, dε_gp, state_gp_old) Now, using the constitutive relation at a generic point, we build a related function which takes cell-wise fe-related quantities, e.g. fe functions, cell fields, etc. The julia function will have this signature σ_fe = constitutive_relation_fe!(state_fe,ε_fe) Here, We can also implement the corresponding linearization σ_fe, dσ_fe = constitutive_relation_fe!(state_fe,ε_fe,dε_fe) With this, the corresponding state_fe = # the user defines the inital state before functions res and jac
function res(u,v)
ε_fe = ε(u)
σ_fe = constitutive_relation_fe!(state_fe, ε_fe)
inner(ε(v), σ_fe) - inner(v,f)
end
function jac(u,v,du)
ε_fe = ε(u)
dε_fe = ε(du)
σ_fe, dσ_fe = constitutive_relation_fe!(state_fe, ε_fe, dε_fe)
inner(ε(v), dσ_fe)
end
Remarks
|
Can you provide more details about the usage of |
Note that Here an example of how to implement struct StateFE
state_dofs
state_space
σ_dofs
σ_space
end
function constitutive_relation_fe!(state_fe::StateFE, ε_fe)
σ_space = state_fe.σ_space
state_dofs = state_fe.state_dofs
σ_dofs = state_fe.σ_dofs
ε_dofs = free_values(interpolate(ε_fe, σ_space))
_fill_sigma_and_state!(state_dofs, σ_dofs, ε_dofs)
σ_fe = FEFunction(σ_space,σ_dofs)
return σ_fe
end
function _fill_sigma_and_state!(state_dofs, σ_dofs, ε_dofs)
for gp in 1:length(σ_dofs)
ε_gp = ε_dofs[gp]
state_gp_old = state_dofs[gp]
σ_gp, state_gp_new = constitutive_relation_gp(ε_gp, state_gp_old)
σ_dofs[gp] = σ_gp
state_dofs[gp] = state_gp_new
end
end
For the linearized version, it would be analogous. |
The difference is that we need to return a cell basis instead of a cell field. |
What is the analogous of You want a cell array such that at every cell at every Gauss points provides you with a method that applies the constitutive tensor |
Yes, we need to build a CellBasis with tensorial value that represents To think how to solve it. |
OK, at least we have now a better understanding of what we need. I will mark the issue as new functionality. |
@santiagobadia I have implemented a way of using constitutive laws with state variables. The main buildings block are two new E.g., for a Poisson problem on could create the following FE term: using Gridap
using Gridap.FESpaces # needed to use the advanced function
function cellmatvec!(mat,vec,∇v,∇u,v,j,w)
Q = length(w)
M,N = size(mat)
for q in 1:Q
dV = det(j[q])*w[q]
for n in 1:N
for m in 1:M
mat[m,n] += ∇v[q,m]*∇u[q,n]*dV
end
end
for m in 1:M
vec[m] += v[q,m]*dV
end
end
end
trian = ...
quad = ...
q = get_coordinates(quad)
w_q = get_weights(quad)
ϕ = get_cell_map(trian)
jac = ∇(ϕ)
jac_q = evaluate(jac,q)
x_q = evaluate(ϕ,q)
function cellmatvec(v,u)
v_q = evaluate(v,q)
∇v_q = evaluate(∇(v),q)
apply_cellmatvec(cellmatvec!, ∇v_q, ∇v_q, v_q, jac_q, w_q, x_q)
end
t_Ω = AffineFETermFromCellMatVec(cellmatvec,trian)
This will be useful (of course not in the previous case) in situations where the user needs full control (e.g., with state variables). E.g., a p-laplacian driver with a dummy state variable: using Test
using LinearAlgebra
using Gridap
import Gridap: ∇
# Needed to use some advanced functions
using Gridap.FESpaces
domain = (1,2,1,2)
partition =(3,3)
model = CartesianDiscreteModel(domain,partition)
u(x) = x[1] + x[2]
∇u(x) = VectorValue(1,1)
∇(::typeof(u)) = ∇u
const p = 3
flux(∇u) = norm(∇u)^(p-2) * ∇u
dflux(∇du,∇u) = (p-2)*norm(∇u)^(p-4)*inner(∇u,∇du)*∇u + norm(∇u)^(p-2)*∇du
order = 1
T = Float64
V = TestFESpace(
model=model,
order=order,
reffe=:Lagrangian,
conformity=:H1,
valuetype=T,
dirichlet_tags="boundary")
U = TrialFESpace(V,u)
degree = 2*order
trian = get_triangulation(model)
quad = CellQuadrature(trian,degree)
# Prepare some quantities at gp level
q = get_coordinates(quad)
w_q = get_weights(quad)
ϕ = get_cell_map(trian)
j_q = evaluate(∇(ϕ),q)
# Init the state variables at gp (a float per gp)
s_q = [ zeros(Float64,size(qi)) for qi in q ]
# Definition of the integration of the jacobian and residual at cell level
function celljacres!(jac,res,∇v,∇du,∇uh,j,w,s)
Q = length(w)
M,N = size(jac)
for q in 1:Q
dV = abs(det(j[q]))*w[q]
flux_q = flux(∇uh[q])
# update the state variable with a dummy expression
s[q] = max(s[q],norm(flux_q))
for m in 1:M
for n in 1:N
jac[m,n] += ∇v[q,m]*dflux(∇du[q,n],∇uh[q])*dV
end
res[m] += ∇v[q,m]*flux_q*dV
end
end
end
# Build a FETerm from the definition of the cell jacobian and residual
function celljacres(uh,v,du)
# Prepare remaining quantities at gp level
∇uh_q = evaluate(∇(uh),q)
∇du_q = evaluate(∇(du),q)
∇v_q = evaluate(∇(v),q)
# This builds an array of tupes (jac,res) representing the contribution of each cell
apply_cellmatvec(celljacres!,∇v_q,∇du_q,∇uh_q,j_q,w_q,s_q)
end
t_Ω = FETermFromCellJacRes(celljacres,trian)
op = FEOperator(V,U,t_Ω)
nls = NLSolver(show_trace=true, method=:newton)
solver = FESolver(nls)
x = rand(T,num_free_dofs(U))
uh0 = FEFunction(U,x)
uh, = solve!(uh0,solver,op)
# Print state variables and solution
x_q = evaluate(ϕ,q)
writevtk(x_q,"x",nodaldata=["s"=>s_q])
writevtk(trian,"trian",nsubcells=order,cellfields=["uh"=>uh])
e = u - uh
l2(u) = inner(u,u)
sh1(u) = inner(∇(u),∇(u))
h1(u) = sh1(u) + l2(u)
el2 = sqrt(sum( integrate(l2(e),trian,quad) ))
eh1 = sqrt(sum( integrate(h1(e),trian,quad) ))
ul2 = sqrt(sum( integrate(l2(uh),trian,quad) ))
uh1 = sqrt(sum( integrate(h1(uh),trian,quad) ))
@test el2/ul2 < 1.e-8
@test eh1/uh1 < 1.e-7
The same can also be done in multi field computations. Eg. function cellmatvec!(A,B,y,x,j,w)
A_vu = A[1,1]
A_vp = A[1,2]
A_qp = A[2,2]
B_v = B[1]
B_q = B[2]
u = x[1]
p = x[2]
v = y[1]
q = y[2]
N_v, N_u = size(A_vu)
N_q, N_p = size(A_qp)
S = length(w)
for s in 1:S
dV = det(j[s])*w[s]
for n_v in 1:N_v
for n_u in 1:N_u
A_vu[n_v,n_u] += v[s,n_v]*u[s,n_u]*dV
end
for n_p in 1:N_p
A_vp[n_v,n_p] += v[s,n_v]*p[s,n_p]*dV
end
end
for n_q in 1:N_q
for n_p in 1:N_p
A_qp[n_q,n_p] -= q[s,n_q]*p[s,n_p]*dV
end
end
for n_v in 1:N_v
B_v[n_v] += v[s,n_v]*4*dV
end
for n_q in 1:N_q
B_q[n_q] += q[s,n_q]*dV
end
end
end
TODO: For the moment, not for integrals on the skeleton (Work not yet in master) |
This PR contains a first solution for issue #155
This development is good, because it provides flexibility. In any case, I will still think we should develop an integration point definition of the residual and Jacobian in the spirit of the discussion above. It would be more in line with the standard usage of the library. Don't you think so? |
Yes, I have done this in order to have an "advanced" mode of defining cell matrices and vectors which provides full flexibility and also improved performance in some cases since it does not introduce complex operation trees. In any case, I am thinking about implementing a macro By the way, I have realized that trying to compute the residual and Jacobin simultaneously is actually counterproductive. The |
I have implemented the Example: using Test
using LinearAlgebra
using Gridap
import Gridap: ∇
domain = (1,2,1,2)
partition =(3,3)
model = CartesianDiscreteModel(domain,partition)
u(x) = x[1] + x[2]
∇u(x) = VectorValue(1,1)
∇(::typeof(u)) = ∇u
const p = 3
# The function annotated with @statelaw should return also the updated state
# which will be set internally into the corresponding array
@statelaw function flux(∇u,s)
f = norm(∇u)^(p-2) * ∇u
new_s = max(s,norm(f))
f, new_s
end
@law dflux(∇du,∇u) = (p-2)*norm(∇u)^(p-4)*inner(∇u,∇du)*∇u + norm(∇u)^(p-2)*∇du
order = 3
T = Float64
V = TestFESpace(
model=model,
order=order,
reffe=:Lagrangian,
conformity=:H1,
valuetype=T,
dirichlet_tags="boundary")
U = TrialFESpace(V,u)
degree = 2*order
trian = get_triangulation(model)
quad = CellQuadrature(trian,degree)
# Initialize the state variable
s = QPointCellField(0.0,trian,quad)
# Note the call flux(∇(u),s)
res(u,v) = inner( ∇(v), flux(∇(u),s) )
jac(u,v,du) = inner( ∇(v) , dflux(∇(du),∇(u)) )
t_Ω = FETerm(res,jac,trian,quad)
op = FEOperator(V,U,t_Ω)
nls = NLSolver(show_trace=false, method=:newton)
solver = FESolver(nls)
x = rand(T,num_free_dofs(U))
uh0 = FEFunction(U,x)
uh, = solve!(uh0,solver,op)
# Visualize state variable ad gps
q = get_coordinates(quad)
x = get_physical_coordinate(trian)
x_q = evaluate(x,q)
s_q = evaluate(s,q)
writevtk(x_q,"x",nodaldata=["state"=>s_q])
# Visualize state variable as a field using a L2 projection into the solution space
t_l2 = AffineFETerm((v,u)->v*u, (v) -> v*s, trian, quad)
op_l2 = AffineFEOperator(V,U,t_l2)
sh = solve(op_l2)
writevtk(trian,"trian",nsubcells=order,cellfields=["uh"=>uh,"state"=>sh])
e = u - uh
l2(u) = inner(u,u)
sh1(u) = inner(∇(u),∇(u))
h1(u) = sh1(u) + l2(u)
el2 = sqrt(sum( integrate(l2(e),trian,quad) ))
eh1 = sqrt(sum( integrate(h1(e),trian,quad) ))
@test el2 < 1.e-8
@test eh1 < 1.e-7
|
Using the https://github.com/gridap/Tutorials/blob/damage_model_tutorial/src/damage_model.jl |
In our last meeting, we have decided that the update of the state variables, which is enforced by using a On the other hand, the damage mode in |
First damage model working in Thanks to @macaicedos was quite straight forward to set-up the problem I need to add some test in Gridap that tests this functionality and then I'll close the issue. |
Finally, the update is not at the Jacobian level within the non-linear solver. The state variables are updated at the load step level, not within the non-linear solver |
…riables Final retouches for historical variables. Fixes issue #155
I have created a driver for isotropic damage model. It is not working yet, since we still have to decide how to implement the historic variables within Gridap. But I think that the essence of the method and design issues are clear
The text was updated successfully, but these errors were encountered: