Rubber stretch¶

Open In Colab Binder

We load an ISO37 specimen with a Neo-Hookean material.

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()
No description has been provided for this image
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.
No description has been provided for this image