Truss Topology Optimization

Description

Truss topology optimization finds the optimal layout of bar elements (trusses) that minimizes compliance (maximizes stiffness) subject to a volume constraint. Unlike continuum topology optimization which uses density-based methods, truss optimization works with discrete bar elements whose cross-sectional areas are the design variables.

This tutorial demonstrates truss optimization using a JSON input file. The truss optimization framework supports:

  • 2D and 3D truss structures
  • Multiple load cases
  • Stress constraints (optional)
  • Direct integration with Nonconvex.jl optimizers

Setup

Load the required packages:

using TopOpt, LinearAlgebra
using CairoMakie
Precompiling packages...
   2633.8 msQuartoNotebookWorkerTablesExt (serial)
  1 dependency successfully precompiled in 3 seconds
Precompiling packages...
   1314.5 msQuartoNotebookWorkerJSONExt (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   1272.0 msQuartoNotebookWorkerJSON3Ext (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   1350.3 msQuartoNotebookWorkerLaTeXStringsExt (serial)
  1 dependency successfully precompiled in 2 seconds
Precompiling packages...
   6814.2 msQuartoNotebookWorkerMakieExt (serial)
  1 dependency successfully precompiled in 7 seconds
Precompiling packages...
   5649.3 msQuartoNotebookWorkerCairoMakieExt (serial)
  1 dependency successfully precompiled in 6 seconds

Load the truss problem from JSON

Truss problems are defined by node coordinates, element connectivity, material properties, cross-sectional areas, boundary conditions, and load cases. These can be loaded from a JSON file:

ndim = 2
node_points, elements, mats, crosssecs, fixities, load_cases = load_truss_json(
    "path/to/truss_problem.json"
)
nnodes = length(node_points)
ncells = length(elements)
loads = load_cases["1"]  # first load case

The JSON file contains:

  • node_points: coordinates of all nodes
  • elements: connectivity (node indices for each bar)
  • mats: Young’s modulus for each element
  • crosssecs: initial cross-sectional areas
  • fixities: fixed degrees of freedom at nodes
  • load_cases: named load vectors

Create the TrussProblem

problem = TrussProblem(
    Val{:Linear}, node_points, elements, loads, fixities, mats, crosssecs
)

The Val{:Linear} specifies linear truss elements (axial deformation only). The problem object assembles the global stiffness matrix and applies boundary conditions.

Define design variables and constraints

xmin = 0.0001 # minimum cross-sectional area (prevents singularity)
x0 = fill(1.0, ncells) # initial design (uniform bars)
p = 4.0 # power-law penalty exponent
V = 0.5 # maximum volume fraction (50% of initial material)

The penalty exponent p=4.0 drives the design toward discrete 0/1 solutions (void bars or full-size bars).

Define the FEA solver and objective

solver = FEASolver(DirectSolver, problem; xmin=xmin)
comp = ComplianceFun(solver)

Define objective and constraint functions

function obj(x)
    # minimize compliance
    return comp(PseudoDensities(x))
end

function constr(x)
    # volume fraction constraint
    return sum(x) / length(x) - V
end

Build the Nonconvex model and optimize

m = Model(obj)
addvar!(m, zeros(length(x0)), ones(length(x0)))  # bounds: 0 ≤ xᵢ ≤ 1
Nonconvex.add_ineq_constraint!(m, constr)

options = MMAOptions(; maxiter=100, tol=Tolerance(; kkt=1e-4, f=1e-4))
setpenalty!(solver, p)
r = Nonconvex.optimize(
    m, MMA87(; dualoptimizer=ConjugateGradient()), x0; options=options
)

The MMA87 optimizer with conjugate gradient dual solver efficiently handles the large-scale nonlinear problem.

Check results

@show obj(r.minimizer)
@show constr(r.minimizer)

Visualize the optimized truss

fig = visualize(problem; solver.u, topology=r.minimizer, default_exagg_scale=0.0)
Figure 1

The visualization shows the optimized truss layout — bars with larger cross- sectional areas carry more load, while inefficient bars are removed (area → 0).

Example JSON file format

A sample 2D truss JSON file (tim_2d.json):

{
  "node_points": {
    "1": [0.0, 0.0],
    "2": [0.0, 1.0],
    "3": [1.0, 1.0]
  },
  "elements": {
    "1": [1, 2],
    "2": [2, 3],
    "3": [1, 3]
  },
  "mats": [1.0, 1.0, 1.0],
  "crosssecs": [1.0, 1.0, 1.0],
  "fixities": {
    "1": [0.0, 0.0],
    "2": [0.0, 0.0]
  },
  "load_cases": {
    "1": {
      "3": [0.0, -1.0]
    }
  }
}

Load it with:

Download the sample truss file: tim_2d.json.

node_points, elements, mats, crosssecs, fixities, load_cases = load_truss_json(
    joinpath(@__DIR__, "tim_2d.json")
)