RasmahRasmah

Graded-Lattice (Infill) Topology Optimization

println("Julia ", VERSION)
Julia 1.12.6

Overview

A graded lattice is a structure whose micro-architecture — a periodic lattice of struts — varies smoothly from fully solid to open where material is not needed. Instead of interpolating stiffness with the ad-hoc SIMP power law ρᵖ, homogenization-based infill (Groen & Sigmund 2018; Wu, Sigmund & Groen 2021) first computes the true effective stiffness C_H(ρ) of a lattice unit cell at a range of lattice densities ρ, and then interpolates that table through the optimization.

In Rasmah this is a GradedLatticePenalty <: AbstractPenalty: the design variable is the local lattice density, and its effective stiffness is the monotone-cubic (PCHIP) interpolation of the homogenized C_H(ρ) table. Because it implements the same value/derivative interface as SIMP, the whole AffineStateMap + adjoint machinery drives it unchanged.

using Rasmah
using LinearAlgebra

part  = box(2.0, 0.5, 0.5)
b     = brep(part)
left  = faces_in_box(b, [-1.1, -1.0, -1.0], [-0.9, 1.0, 1.0])
right = faces_in_box(b, [ 0.9, -1.0, -1.0], [ 1.1, 1.0, 1.0])
mesh  = tetrahedralize(b, 8)
TetMesh{Matrix{Float64}, Matrix{Int64}, Rasmah.BRepFaceLabels}([-1.0 -0.75 … 0.75 1.0; -0.25 -0.25 … 0.25 0.25; -0.25 -0.25 … 0.25 0.25], [1 1 … 44 44; 2 11 … 71 72; 11 10 … 72 45; 38 38 … 81 81], Rasmah.BRepFaceLabels(FaceSet[FaceSet([16 3 … 15 4; 25 4 … 24 13; 26 13 … 25 14]), FaceSet([61 61 … 65 70; 62 70 … 74 79; 71 71 … 75 80]), FaceSet([29 30 … 4 32; 30 31 … 5 33; 57 58 … 32 60]), FaceSet([52 24 … 51 19; 79 25 … 78 46; 80 52 … 79 47]), FaceSet([28 10 … 37 1; 37 19 … 46 10; 64 46 … 73 37]), FaceSet([45 36 … 18 9; 54 45 … 45 18; 81 72 … 54 45])]))

solve(GradedLatticeTopOpt(), …) builds a cross-lattice unit cell at resolution cell_n, tabulates its homogenized stiffness, and optimizes the macro density so each element’s effective material follows that graded-lattice response.

φ, hist = solve(GradedLatticeTopOpt(), mesh, steel;
                fixed = left => [0, 0, 0], loads = right => [1e6, 0, 0],
                volfrac = 0.4, cell_n = 6, max_iter = 40)

println("compliance: ", round(hist[1]; digits = 3), " → ", round(hist[end]; digits = 3))
println("final volume frac = ", round(sum(φ) / length(φ); digits = 3))

vtk(mesh; field = φ, fieldname = "lattice density", edges = true)
compliance: 38.428 → 6.974
final volume frac = 0.4

Verification

The homogenization table is anchored at the solid end: a fully-dense lattice (ρ = 1) reproduces the solid material exactly, so the penalty interpolant must evaluate to 1 there — and its derivative must match finite differences:

cell = tetrahedralize_box(1.0, 1.0, 1.0, 6, 6, 6)
C    = Matrix(elasticity_matrix(1e3, 0.3))
ρs, sc = lattice_stiffness_table(cell, C, range(0.01, 1.0; length = 17); ρmin = 0.01, t = 0.5, p = 3.0)
pen = GradedLatticePenalty(ρs, sc)

println("value at ρ = 1  = ", value(pen, 1.0), "  (must be 1)")
h = 1e-6
println("dvalue at ρ = 0.5: analytic ", round(derivative(pen, 0.5); digits = 6),
        ", finite-diff ", round((value(pen, 0.5 + h) - value(pen, 0.5 - h)) / (2h); digits = 6))
value at ρ = 1  = 1.0  (must be 1)
dvalue at ρ = 0.5: analytic 0.745784, finite-diff 0.745784

Next steps