In [1]:
import torch
from scipy.optimize import bisect
from tqdm import tqdm
from torchfem import Solid
from torchfem.materials import IsotropicElasticity3D
from torchfem.mesh import cube_hexa
torch.set_default_dtype(torch.float64)
Model setup¶
We start by defining the base problem without considering the optimization yet.
In [2]:
# Material model
material = IsotropicElasticity3D(E=100.0, nu=0.3)
Nx = 20
Ny = 10
Nz = 15
# Create mesh
nodes, elements = cube_hexa(Nx + 1, Ny + 1, Nz + 1, Nx, Ny, Nz)
model = Solid(nodes, elements, material)
# Load at tip
tip = nodes[:, 0] == Nx
bottom = nodes[:, 2] == 0
model.forces[tip & bottom, 2] = -1.0
model.forces[tip & bottom & (nodes[:, 1] == 0), 2] = -0.5
model.forces[tip & bottom & (nodes[:, 1] == Ny), 2] = -0.5
# Constrained displacement at left end
model.constraints[nodes[:, 0] == 0.0, :] = True
# Solve
u, f, σ, F, state = model.solve()
# Plot
model.plot(u=u, node_property={"U": u}, bcs=True)
EmbeddableWidget(value='<iframe srcdoc="<!doctype html>\n<html lang="en">\n <head>\n <meta chars…
Optimization parameters¶
We define the optimization parameters, i.e. the volume fraction, the penalization factor, the move limit, the filter radius, and the number of iterations.
In [3]:
volfrac = 0.5
p = 3
move = 0.2
R = 1.5
TORCH_SENS = False
In [4]:
# Initial, minimum, and maximum values of design variables
rho_0 = volfrac * torch.ones(len(elements))
rho_min = 0.05 * torch.ones_like(rho_0)
rho_max = torch.ones_like(rho_0)
# Volume fraction
V_0 = volfrac * Nx * Ny * Nz
# Analytical gradient of the stiffness matrix
k0 = model.k0()
C0 = model.material.C.clone()
# Precompute filter weights
ecenters = nodes[elements].mean(dim=1)
dist = torch.cdist(ecenters, ecenters)
H = R - dist
H[dist > R] = 0.0
Optimization with optimality constraints.¶
This may take a minute to run.
In [5]:
rho = [rho_0]
# Iterate solutions
for k in tqdm(range(20)):
# Adjust thickness variables
model.material.C = torch.einsum("n,nijkl->nijkl", rho[k] ** p, C0)
# Compute solution
u_k, f_k, _, _, _ = model.solve()
# Evaluation of compliance
compliance = torch.inner(f_k.ravel(), u_k.ravel())
# Compute analytical sensitivities
u_j = u_k[elements].reshape(model.n_elem, -1)
w_k = torch.einsum("...i, ...ij, ...j", u_j, k0, u_j)
sensitivity = -p * rho[k] ** (p - 1.0) * w_k
# Filter sensitivities (if r provided)
sensitivity = H @ (rho[k] * sensitivity) / H.sum(dim=0) / rho[k]
# For a certain value of mu, apply the iteration scheme
def make_step(mu):
G_k = -sensitivity / mu
upper = torch.min(rho_max, (1 + move) * rho[k])
lower = torch.max(rho_min, (1 - move) * rho[k])
rho_trial = G_k**0.5 * rho[k]
return torch.maximum(torch.minimum(rho_trial, upper), lower)
# Constraint function
def g(mu):
rho_k = make_step(mu)
return rho_k.sum() - V_0
# Find the root of g(mu)
with torch.no_grad():
mu = bisect(g, 1e-10, 100.0)
rho.append(make_step(mu))
100%|██████████| 20/20 [00:08<00:00, 2.27it/s]
In [6]:
model.plot(
element_property={"rho": rho[-1]},
clip=("rho", 0.5),
show_edges=False,
show_outline=True,
color="lightblue",
bcs=True,
)
EmbeddableWidget(value='<iframe srcdoc="<!doctype html>\n<html lang="en">\n <head>\n <meta chars…