using NonconvexTOBS, TopOptTOBS: Topological Optimization of Binary Structures
Description
TOBS (Topological Optimization of Binary Structures) is a heuristic algorithm for binary topology optimization. Unlike SIMP which uses continuous densities, TOBS directly optimizes 0/1 (void/solid) designs by linearizing the objective and constraints, then solving a binary nonlinear program. The default solver is Cbc.jl (COIN-OR Branch and Cut), which determines which binary variables must be flipped in each iteration.
This tutorial demonstrates TOBS on a 2D cantilever beam with 80×50 elements (4,000 design variables). The algorithm efficiently handles the combinatorial challenge of binary optimization through sequential linearization and branch- and-cut solving.
Setup
Problem Definition
We use a large 2D cantilever beam to demonstrate TOBS scalability:
E = 1.0 # Young's modulus
v = 0.3 # Poisson's ratio
f = 1.0 # downward force
rmin = 6.0 # filter radius
xmin = 0.001 # minimum density
V = 0.5 # maximum volume fraction
p = 3.0 # topological optimization penalty
problem_size = (80, 50) # 4,000 elements
x0 = fill(1.0, prod(problem_size)) # start from fully solid design
problem = PointLoadCantilever(Val{:Linear}, problem_size, (1.0, 1.0), E, v, f)The mesh (80×50) tests TOBS ability to handle high-dimensional binary optimization problems.
FEA Solver and Filter
solver = FEASolver(DirectSolver, problem; xmin=xmin)
cheqfilter = DensityFilterFun(solver; rmin=rmin) # mesh-independent filtering
comp = ComplianceFun(solver)The density filter ensures smooth designs and prevents checkerboarding, critical for binary optimization where small features can cause numerical instability.
Objective and Constraint
obj(x) = comp(cheqfilter(PseudoDensities(x))) # compliance objective
constr(x) = sum(cheqfilter(PseudoDensities(x))) / length(x) - V # volume constraintThe objective minimizes compliance (strain energy) while the constraint limits material usage to 50% of the domain volume.
Optimization Setup
m = Model(obj)
addvar!(m, zeros(length(x0)), ones(length(x0))) # bounds: 0 ≤ xᵢ ≤ 1
Nonconvex.add_ineq_constraint!(m, constr)
# TOBS starts from the provided x0 and flips at most
# `movelimit * numVars` variables per iteration, so reaching the 50%
# volume target from 100% solid needs enough iterations to remove half
# the material.
options = TOBSOptions(; maxiter=100, movelimit=0.05)
setpenalty!(solver, p)TOBS uses binary variables (0 or 1) with bound constraints. The penalty exponent p=3.0 penalizes intermediate densities in the FEA solution.
Run TOBS Optimization
@time r = Nonconvex.optimize(m, TOBSAlg(), x0; options=options)┌ Warning: Subproblem is infeasible. Temporarily relaxing the subproblem. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:188 ┌ Warning: Subproblem is infeasible. Temporarily relaxing the subproblem. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:188 ┌ Warning: Subproblem is infeasible. Temporarily relaxing the subproblem. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:188 ┌ Warning: Subproblem is infeasible. Temporarily relaxing the subproblem. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:188 ┌ Warning: Subproblem infeasible 5 times. Switching to feasibility restoration. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:180 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 ┌ Warning: Restoration subproblem still infeasible. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:184 [ Info: Restoration step succeeded; resuming normal iterations. [ Info: iter = 26, obj = 809.072, constr_vio_norm = 0.5, er = 1.0 [ Info: iter = 27, obj = 361.466, constr_vio_norm = 0.0, er = 1.074 [ Info: iter = 28, obj = 238.94, constr_vio_norm = 0.0, er = 0.979 [ Info: iter = 29, obj = 453.56, constr_vio_norm = 0.0, er = 0.855 [ Info: iter = 30, obj = 768.28, constr_vio_norm = 0.0, er = 0.725 [ Info: iter = 31, obj = 334.888, constr_vio_norm = 0.0, er = 0.79 [ Info: iter = 32, obj = 826.699, constr_vio_norm = 0.0, er = 0.747 [ Info: iter = 33, obj = 340.523, constr_vio_norm = 0.0, er = 0.803 [ Info: iter = 34, obj = 281.652, constr_vio_norm = 0.0, er = 0.765 [ Info: iter = 35, obj = 312.759, constr_vio_norm = 0.0, er = 0.721 [ Info: iter = 36, obj = 176.276, constr_vio_norm = 0.0, er = 0.723 [ Info: iter = 37, obj = 159.953, constr_vio_norm = 0.0, er = 0.704 [ Info: iter = 38, obj = 95.858, constr_vio_norm = 0.0, er = 0.703 [ Info: iter = 39, obj = 100.99, constr_vio_norm = 0.0, er = 0.69 [ Info: iter = 40, obj = 68.532, constr_vio_norm = 0.0, er = 0.688 [ Info: iter = 41, obj = 57.777, constr_vio_norm = 0.0, er = 0.682 [ Info: iter = 42, obj = 60.158, constr_vio_norm = 0.0, er = 0.675 [ Info: iter = 43, obj = 60.567, constr_vio_norm = 0.0, er = 0.668 [ Info: iter = 44, obj = 67.621, constr_vio_norm = 0.0, er = 0.661 [ Info: iter = 45, obj = 56.597, constr_vio_norm = 0.0, er = 0.513 [ Info: iter = 46, obj = 62.73, constr_vio_norm = 0.0, er = 0.501 [ Info: iter = 47, obj = 55.371, constr_vio_norm = 0.0, er = 0.509 [ Info: iter = 48, obj = 52.237, constr_vio_norm = 0.0, er = 0.482 [ Info: iter = 49, obj = 58.312, constr_vio_norm = 0.0, er = 0.453 [ Info: iter = 50, obj = 58.744, constr_vio_norm = 0.0, er = 0.419 [ Info: iter = 51, obj = 53.642, constr_vio_norm = 0.0, er = 0.296 [ Info: iter = 52, obj = 59.283, constr_vio_norm = 0.0, er = 0.183 [ Info: iter = 53, obj = 54.316, constr_vio_norm = 0.0, er = 0.182 [ Info: iter = 54, obj = 51.577, constr_vio_norm = 0.0, er = 0.19 [ Info: iter = 55, obj = 57.258, constr_vio_norm = 0.0, er = 0.134 [ Info: iter = 56, obj = 55.691, constr_vio_norm = 0.0, er = 0.135 [ Info: iter = 57, obj = 52.708, constr_vio_norm = 0.0, er = 0.098 [ Info: iter = 58, obj = 58.258, constr_vio_norm = 0.0, er = 0.101 [ Info: iter = 59, obj = 52.669, constr_vio_norm = 0.0, er = 0.082 [ Info: iter = 60, obj = 51.585, constr_vio_norm = 0.0, er = 0.075 [ Info: iter = 61, obj = 54.215, constr_vio_norm = 0.0, er = 0.075 [ Info: iter = 62, obj = 55.121, constr_vio_norm = 0.0, er = 0.076 [ Info: iter = 63, obj = 51.462, constr_vio_norm = 0.0, er = 0.073 [ Info: iter = 64, obj = 56.332, constr_vio_norm = 0.0, er = 0.069 [ Info: iter = 65, obj = 50.681, constr_vio_norm = 0.0, er = 0.069 [ Info: iter = 66, obj = 51.047, constr_vio_norm = 0.0, er = 0.063 [ Info: iter = 67, obj = 51.883, constr_vio_norm = 0.0, er = 0.061 [ Info: iter = 68, obj = 52.359, constr_vio_norm = 0.0, er = 0.056 [ Info: iter = 69, obj = 50.474, constr_vio_norm = 0.0, er = 0.058 [ Info: iter = 70, obj = 56.372, constr_vio_norm = 0.0, er = 0.058 [ Info: iter = 71, obj = 49.746, constr_vio_norm = 0.0, er = 0.06 [ Info: iter = 72, obj = 50.451, constr_vio_norm = 0.0, er = 0.056 [ Info: iter = 73, obj = 50.118, constr_vio_norm = 0.0, er = 0.054 [ Info: iter = 74, obj = 51.69, constr_vio_norm = 0.0, er = 0.05 [ Info: iter = 75, obj = 49.777, constr_vio_norm = 0.0, er = 0.051 [ Info: iter = 76, obj = 51.026, constr_vio_norm = 0.0, er = 0.049 [ Info: iter = 77, obj = 49.086, constr_vio_norm = 0.0, er = 0.046 [ Info: iter = 78, obj = 49.173, constr_vio_norm = 0.0, er = 0.041 [ Info: iter = 79, obj = 49.527, constr_vio_norm = 0.0, er = 0.041 [ Info: iter = 80, obj = 51.126, constr_vio_norm = 0.0, er = 0.04 [ Info: iter = 81, obj = 49.51, constr_vio_norm = 0.0, er = 0.041 [ Info: iter = 82, obj = 50.522, constr_vio_norm = 0.0, er = 0.038 [ Info: iter = 83, obj = 49.35, constr_vio_norm = 0.0, er = 0.035 [ Info: iter = 84, obj = 49.319, constr_vio_norm = 0.0, er = 0.029 [ Info: iter = 85, obj = 49.936, constr_vio_norm = 0.0, er = 0.03 [ Info: iter = 86, obj = 50.727, constr_vio_norm = 0.0, er = 0.03 [ Info: iter = 87, obj = 49.379, constr_vio_norm = 0.0, er = 0.03 [ Info: iter = 88, obj = 51.018, constr_vio_norm = 0.0, er = 0.03 [ Info: iter = 89, obj = 48.936, constr_vio_norm = 0.0, er = 0.027 [ Info: iter = 90, obj = 49.051, constr_vio_norm = 0.0, er = 0.02 [ Info: iter = 91, obj = 49.637, constr_vio_norm = 0.0, er = 0.02 [ Info: iter = 92, obj = 50.781, constr_vio_norm = 0.0, er = 0.021 [ Info: iter = 93, obj = 49.223, constr_vio_norm = 0.0, er = 0.021 [ Info: iter = 94, obj = 50.958, constr_vio_norm = 0.0, er = 0.021 [ Info: iter = 95, obj = 48.791, constr_vio_norm = 0.0, er = 0.022 [ Info: iter = 96, obj = 49.08, constr_vio_norm = 0.0, er = 0.02 [ Info: iter = 97, obj = 49.348, constr_vio_norm = 0.0, er = 0.02 [ Info: iter = 98, obj = 51.38, constr_vio_norm = 0.0, er = 0.022 [ Info: iter = 99, obj = 49.005, constr_vio_norm = 0.0, er = 0.023 ┌ Warning: No feasible solution was found during the optimization. Returning the best design found. └ @ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:221 143.853125 seconds (94.35 M allocations: 6.275 GiB, 3.30% gc time, 17.03% compilation time: 14% of which was recompilation)
NonconvexTOBS.TOBSResult{Vector{Float64}, Float64, Float64}([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 … 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], 809.0718703188668, 0.022639768610566623)
The TOBS algorithm iterates:
- Linearize objective and constraints around current design
- Solve binary subproblem with Cbc.jl
- Determine which variables to flip (0→1 or 1→0)
- Update design and repeat
Results
@show obj(r.minimizer)
@show constr(r.minimizer)obj(r.minimizer) = 809.0718703188668
constr(r.minimizer) = -9.252818744531766e-8
-9.252818744531766e-8
The final design should be nearly binary (values close to 0 or 1) with satisfied volume constraint. TOBS typically produces crisp black/white designs without intermediate densities.
Visualization
using CairoMakie
topology = r.minimizer
fig = visualize(problem; topology=topology)Precompiling packages... 7194.3 ms ✓ QuartoNotebookWorkerMakieExt (serial) 1 dependency successfully precompiled in 7 seconds Precompiling packages... 5869.1 ms ✓ QuartoNotebookWorkerCairoMakieExt (serial) 1 dependency successfully precompiled in 6 seconds
The visualization shows the final binary structure — TOBS naturally produces clean 0/1 designs without the gray regions common in SIMP results.