TOBS: 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

using NonconvexTOBS, TopOpt
using WGLMakie
WGLMakie.activate!(; resize_to=:parent)
using Bonito
if haskey(ENV, "QUARTO_PROJECT_DIR")
    Bonito.Page(exportable=true, offline=true)
else
    Bonito.browser_display()
end
display_app(app) = display(app)

WGLMakie.activate!(; resize_to=:parent) selects the browser renderer and fills the Quarto output column. Bonito.Page(exportable=true, offline=true) embeds the assets needed by visualize(...; static=true) in the Quarto output, so the visualization does not require a running Julia process.

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(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 constraint

The 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 = 239.047, constr_vio_norm = 0.0, er = 0.978
[ Info: iter = 29, obj = 454.029, constr_vio_norm = 0.0, er = 0.855
[ Info: iter = 30, obj = 768.805, constr_vio_norm = 0.0, er = 0.725
[ Info: iter = 31, obj = 330.695, constr_vio_norm = 0.0, er = 0.792
[ Info: iter = 32, obj = 817.734, constr_vio_norm = 0.0, er = 0.75
[ Info: iter = 33, obj = 338.705, constr_vio_norm = 0.0, er = 0.804
[ Info: iter = 34, obj = 272.602, constr_vio_norm = 0.0, er = 0.769
[ Info: iter = 35, obj = 310.148, constr_vio_norm = 0.0, er = 0.727
[ Info: iter = 36, obj = 166.062, constr_vio_norm = 0.0, er = 0.731
[ Info: iter = 37, obj = 141.06, constr_vio_norm = 0.0, er = 0.716
[ Info: iter = 38, obj = 106.67, constr_vio_norm = 0.0, er = 0.708
[ Info: iter = 39, obj = 86.677, constr_vio_norm = 0.0, er = 0.7
[ Info: iter = 40, obj = 66.907, constr_vio_norm = 0.0, er = 0.695
[ Info: iter = 41, obj = 59.58, constr_vio_norm = 0.0, er = 0.688
[ Info: iter = 42, obj = 54.321, constr_vio_norm = 0.0, er = 0.682
[ Info: iter = 43, obj = 59.937, constr_vio_norm = 0.0, er = 0.676
[ Info: iter = 44, obj = 62.358, constr_vio_norm = 0.0, er = 0.668
[ Info: iter = 45, obj = 55.238, constr_vio_norm = 0.0, er = 0.518
[ Info: iter = 46, obj = 66.603, constr_vio_norm = 0.0, er = 0.507
[ Info: iter = 47, obj = 58.876, constr_vio_norm = 0.0, er = 0.515
[ Info: iter = 48, obj = 60.67, constr_vio_norm = 0.0, er = 0.487
[ Info: iter = 49, obj = 56.071, constr_vio_norm = 0.0, er = 0.458
[ Info: iter = 50, obj = 75.853, constr_vio_norm = 0.0, er = 0.427
[ Info: iter = 51, obj = 57.214, constr_vio_norm = 0.0, er = 0.309
[ Info: iter = 52, obj = 58.897, constr_vio_norm = 0.0, er = 0.199
[ Info: iter = 53, obj = 57.608, constr_vio_norm = 0.0, er = 0.194
[ Info: iter = 54, obj = 54.17, constr_vio_norm = 0.0, er = 0.199
[ Info: iter = 55, obj = 61.644, constr_vio_norm = 0.0, er = 0.14
[ Info: iter = 56, obj = 55.496, constr_vio_norm = 0.0, er = 0.137
[ Info: iter = 57, obj = 55.551, constr_vio_norm = 0.0, er = 0.119
[ Info: iter = 58, obj = 54.268, constr_vio_norm = 0.0, er = 0.109
[ Info: iter = 59, obj = 56.967, constr_vio_norm = 0.0, er = 0.097
[ Info: iter = 60, obj = 54.435, constr_vio_norm = 0.0, er = 0.094
[ Info: iter = 61, obj = 55.839, constr_vio_norm = 0.0, er = 0.091
[ Info: iter = 62, obj = 54.682, constr_vio_norm = 0.0, er = 0.088
[ Info: iter = 63, obj = 54.144, constr_vio_norm = 0.0, er = 0.086
[ Info: iter = 64, obj = 53.978, constr_vio_norm = 0.0, er = 0.081
[ Info: iter = 65, obj = 55.207, constr_vio_norm = 0.0, er = 0.072
[ Info: iter = 66, obj = 53.318, constr_vio_norm = 0.0, er = 0.068
[ Info: iter = 67, obj = 52.974, constr_vio_norm = 0.0, er = 0.067
[ Info: iter = 68, obj = 54.08, constr_vio_norm = 0.0, er = 0.064
[ Info: iter = 69, obj = 52.171, constr_vio_norm = 0.0, er = 0.049
[ Info: iter = 70, obj = 54.018, constr_vio_norm = 0.0, er = 0.035
[ Info: iter = 71, obj = 53.146, constr_vio_norm = 0.0, er = 0.034
[ Info: iter = 72, obj = 52.205, constr_vio_norm = 0.0, er = 0.034
[ Info: iter = 73, obj = 51.581, constr_vio_norm = 0.0, er = 0.031
[ Info: iter = 74, obj = 52.314, constr_vio_norm = 0.0, er = 0.025
[ Info: iter = 75, obj = 50.87, constr_vio_norm = 0.0, er = 0.021
[ Info: iter = 76, obj = 53.291, constr_vio_norm = 0.0, er = 0.023
[ Info: iter = 77, obj = 51.705, constr_vio_norm = 0.0, er = 0.024
[ Info: iter = 78, obj = 51.449, constr_vio_norm = 0.0, er = 0.022
[ Info: iter = 79, obj = 50.473, constr_vio_norm = 0.0, er = 0.02
[ Info: iter = 80, obj = 50.606, constr_vio_norm = 0.0, er = 0.019
[ Info: iter = 81, obj = 49.85, constr_vio_norm = 0.0, er = 0.019
[ Info: iter = 82, obj = 51.58, constr_vio_norm = 0.0, er = 0.02
[ Info: iter = 83, obj = 49.598, constr_vio_norm = 0.0, er = 0.022
[ Info: iter = 84, obj = 50.236, constr_vio_norm = 0.0, er = 0.021
[ Info: iter = 85, obj = 49.954, constr_vio_norm = 0.0, er = 0.02
[ Info: iter = 86, obj = 49.389, constr_vio_norm = 0.0, er = 0.02
[ Info: iter = 87, obj = 49.477, constr_vio_norm = 0.0, er = 0.019
[ Info: iter = 88, obj = 50.52, constr_vio_norm = 0.0, er = 0.018
[ Info: iter = 89, obj = 49.231, constr_vio_norm = 0.0, er = 0.018
[ Info: iter = 90, obj = 49.371, constr_vio_norm = 0.0, er = 0.017
[ Info: iter = 91, obj = 50.35, constr_vio_norm = 0.0, er = 0.017
[ Info: iter = 92, obj = 49.006, constr_vio_norm = 0.0, er = 0.018
[ Info: iter = 93, obj = 49.054, constr_vio_norm = 0.0, er = 0.018
[ Info: iter = 94, obj = 51.258, constr_vio_norm = 0.0, er = 0.018
[ Info: iter = 95, obj = 48.969, constr_vio_norm = 0.0, er = 0.018
[ Info: iter = 96, obj = 49.563, constr_vio_norm = 0.0, er = 0.017
[ Info: iter = 97, obj = 49.662, constr_vio_norm = 0.0, er = 0.017
[ Info: iter = 98, obj = 48.964, constr_vio_norm = 0.0, er = 0.017
[ Info: iter = 99, obj = 49.026, constr_vio_norm = 0.0, er = 0.017
Warning: No feasible solution was found during the optimization. Returning the best design found.
@ NonconvexTOBS ~/.julia/packages/NonconvexTOBS/P1EYO/src/NonconvexTOBS.jl:221
139.152387 seconds (93.24 M allocations: 6.223 GiB, 3.20% gc time, 15.60% compilation time: 16% 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.0169051995484461)

The TOBS algorithm iterates:

  1. Linearize objective and constraints around current design
  2. Solve binary subproblem with Cbc.jl
  3. Determine which variables to flip (0→1 or 1→0)
  4. 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

topology = r.minimizer
fig = visualize(problem; static=true, topology=topology)
display_app(fig)
undeformed mesh
load arrows
support arrows
Figure 1: TOBS optimization result showing binary (0/1) material distribution

The visualization shows the final binary structure — TOBS naturally produces clean 0/1 designs without the gray regions common in SIMP results.