In [1]:
import matplotlib.pyplot as plt
import torch
from tqdm import tqdm
from torchfem.data import get_data
from torchfem.io import import_mesh
from torchfem.materials import Hyperelastic3D
torch.set_default_dtype(torch.float64)
# Load per node in top region
F_max = 2.0
# Increments for loading
increments = torch.linspace(0.0, 1.0, 11)
In [2]:
def psi(F, params):
"""Neo-Hookean strain energy density function."""
# Extract material parameters
G = params[0]
D = params[1]
# Compute right Cauchy-Green tensor
C = F.transpose(-1, -2) @ F
# Stable computation of Jacobian
J = torch.exp(0.5 * torch.logdet(C))
C_bar = C * J ** (-2.0 / 3.0)
return G * (torch.trace(C_bar) - 3.0) + 1 / D * (J - 1) ** 2
In [3]:
material = Hyperelastic3D(psi, params=[0.5, 1.0])
# Import mesh
sample = import_mesh(get_data("iso37.vtu"), material)
# Define boundary sets
top = sample.nodes[:, 2] >= -16.0
bottom = sample.nodes[:, 2] <= -99.0
back = sample.nodes[:, 1] == 0.0
front = sample.nodes[:, 1] == 4.0
# Apply boundary conditions
sample.constraints[bottom, :] = True
sample.constraints[back, 1] = True
sample.constraints[top, 0] = True
sample.constraints[top, 1] = True
sample.forces[top, 2] = F_max
sample.plot(bcs=True)
In [4]:
# Solve
u, f, σ, F, α = sample.solve(
increments=increments,
return_intermediate=True,
nlgeom=True,
verbose=True,
)
─── torch-fem · solve ──────────────────────────────────────────────────────────────────
model Solid · 120 elem · 900 dof · float64
machine AMD EPYC 7763 64-Core Processor · 2 threads · 16 GB RAM
solver spsolve · direct · scipy · cpu
newton rtol 1e-08 · atol 1e-06 · ≤10 it · nlgeom
────────────────────────────────────────────────────────────────────────────────────────
Increment Load factor Steps Iterations Residual Wall time
1 0.1 1 4 1.63e-11 1.40 s
2 0.2 1 3 7.19e-09 0.28 s
3 0.3 1 3 9.79e-09 0.29 s
4 0.4 1 3 1.20e-07 0.28 s
5 0.5 1 3 1.06e-07 0.28 s
6 0.6 1 3 1.34e-08 0.28 s
7 0.7 1 3 5.62e-09 0.29 s
8 0.8 1 3 2.98e-09 0.29 s
9 0.9 1 3 3.43e-08 0.28 s
10 1 1 3 1.79e-07 0.28 s
────────────────────────────────────────────────────────────────────────────────────────
converged · 10 increments · 31 iterations · 3.96 s
In [5]:
# Plot results
sample.plot(
u=u[-1],
element_property={"σ_zz": σ[-1, :, 2, 2]},
cmap="inferno",
show_undeformed=True,
)
In [6]:
t_ref = torch.linspace(0.0, 1.0, 11)
u_ref = torch.tensor(
[0.0, 10.16, 23.44, 40.91, 63.55, 91.96, 126.32, 166.61, 212.75, 264.67, 322.31]
)
u_max, _ = u[:, :, 2].detach().max(dim=1)
plt.plot(t_ref, u_ref, ".-", label="Reference", color="gray")
plt.plot(increments, u_max, ".--", label="torch-fem", color="deeppink")
plt.title("Maximum displacement at top")
plt.legend()
plt.xlabel("Increment")
plt.ylabel("Displacement")
plt.grid()
plt.show()
In [7]:
# Set up optimization
G = torch.tensor(1.0, requires_grad=True)
D = torch.tensor(1.0)
optimizer = torch.optim.LBFGS([G], lr=0.5, max_iter=1)
history = []
def closure():
# Reset gradients
optimizer.zero_grad()
# Build params as a tensor to preserve autograd path to G
params = torch.stack((G, D))
sample.material = Hyperelastic3D(psi, params=params).vectorize(sample.n_elem)
# Solve with differentiable parameters and compute loss
u, _, _, _, _ = sample.solve(
increments=increments,
return_intermediate=True,
nlgeom=True,
differentiable_parameters=G,
)
u_top = u[:, top, 2].mean(dim=1)
loss = torch.mean(((u_top - u_ref) / (u_ref + 1e-8)) ** 2)
loss.backward()
# Append training loss
history.append(loss.item())
return loss
for epoch in tqdm(range(12)):
optimizer.step(closure)
print(f"Optimized G: {G.item():.4f}.")
plt.semilogy(history)
plt.xlabel("Epoch")
plt.ylabel("MSE Loss")
plt.title("Training History")
plt.grid()
plt.show()
0%| | 0/12 [00:00<?, ?it/s]
8%|▊ | 1/12 [00:03<00:41, 3.79s/it]
17%|█▋ | 2/12 [00:07<00:37, 3.72s/it]
25%|██▌ | 3/12 [00:11<00:34, 3.82s/it]
33%|███▎ | 4/12 [00:15<00:30, 3.77s/it]
42%|████▏ | 5/12 [00:18<00:26, 3.74s/it]
50%|█████ | 6/12 [00:22<00:22, 3.72s/it]
58%|█████▊ | 7/12 [00:26<00:18, 3.71s/it]
67%|██████▋ | 8/12 [00:29<00:14, 3.70s/it]
75%|███████▌ | 9/12 [00:33<00:11, 3.70s/it]
83%|████████▎ | 10/12 [00:37<00:07, 3.70s/it]
92%|█████████▏| 11/12 [00:40<00:03, 3.69s/it]
100%|██████████| 12/12 [00:44<00:00, 3.68s/it]
100%|██████████| 12/12 [00:44<00:00, 3.71s/it]
Optimized G: 0.4970.