PyMOTO 69-line topology optimization example

Example for a structural compliance topology optimization based on the 99 line Matlab script of Sigmund. Using pyMOTO the number of lines can be reduced to only 69 lines (while providing the same level of detail), which even includes blank lines for readability and additional comments to clarify the functionality of this example. Reference:

Sigmund (2001), “A 99 line topology optimization code written in Matlab” (https://doi.org/10.1007/s001580050176)

 9 import numpy as np
10 import pymoto as pym
11
12 nx, ny = 100, 40  # Size of the domain (number of elements)
13 xmin, filter_radius, volfrac = 1e-3, 1.5, 0.5  # Minimum density, density-filter radius, and volume fraction
14
15 # Compute element stiffness matrix
16 nu, E = 0.3, 1.0
17 ka, kb, kf = 4 * (1 - nu), 2 * (1 - 2 * nu), 6 * nu - 3 / 2
18 el = (E/ (12 * (1 + nu) * (1 - 2 * nu)) * np.array([
19     [ka + kb, -3 / 2, ka / 2 - kb, kf, -ka + kb / 2, -kf, -ka / 2 - kb / 2, 3 / 2],
20     [-3 / 2, ka + kb, -kf, -ka + kb / 2, kf, ka / 2 - kb, 3 / 2, -ka / 2 - kb / 2],
21     [ka / 2 - kb, -kf, ka + kb, 3 / 2, -ka / 2 - kb / 2, -3 / 2, -ka + kb / 2, kf],
22     [kf, -ka + kb / 2, 3 / 2, ka + kb, -3 / 2, -ka / 2 - kb / 2, -kf, ka / 2 - kb],
23     [-ka + kb / 2, kf, -ka / 2 - kb / 2, -3 / 2, ka + kb, 3 / 2, ka / 2 - kb, -kf],
24     [-kf, ka / 2 - kb, -3 / 2, -ka / 2 - kb / 2, 3 / 2, ka + kb, kf, -ka + kb / 2],
25     [-ka / 2 - kb / 2, 3 / 2, -ka + kb / 2, -kf, ka / 2 - kb, kf, ka + kb, -3 / 2],
26     [3 / 2, -ka / 2 - kb / 2, kf, ka / 2 - kb, -kf, -ka + kb / 2, -3 / 2, ka + kb]]))
27
28
29 # OC update scheme
30 def oc_update(x, dfdx):
31     l1, l2, move, maxvol = 0, 100000, 0.2, np.sum(x)
32     while l2 - l1 > 1e-4:
33         lmid = 0.5 * (l1 + l2)
34         xnew = np.clip(x * np.sqrt(-dfdx / lmid), np.maximum(0, x - move), np.minimum(1, x + move))
35         l1, l2 = (lmid, l2) if np.sum(xnew) > maxvol else (l1, lmid)
36     return xnew, np.max(np.abs(xnew - x))
37
38
39 # Setup FE domain
40 domain = pym.VoxelDomain(nx, ny)  # Generate a discretization grid
41 n_left = domain.nodes[0, :].flatten()  # Get node number of all nodes on the left side (x=0)
42 boundary_dofs = np.concatenate([n_left * 2, n_left * 2 + 1])  # Calculate boundary dof indices to fix
43 f = np.zeros(domain.nnodes * 2)  # Generate a force vector
44 f[2 * domain.nodes[-1, ny // 2]] = 1.0  # Set a force in the middle of the right side
45
46 # Initialize input signal for design variables
47 sx = pym.Signal("x", state=np.ones(domain.nel) * volfrac)
48
49 # Start building the modular network; this constructs a function of which we can calculate sensitivities easily
50 with pym.Network() as func:
51     sxfilt = pym.DensityFilter(domain=domain, radius=filter_radius)(sx)  # Density filter
52     sSIMP = pym.MathExpression(expression=f"{xmin} + {1 - xmin}*inp0^3")(sxfilt)  # SIMP material interpolation
53     sK = pym.AssembleGeneral(domain=domain, element_matrix=el, bc=boundary_dofs)(sSIMP)  # Stiffness matrix assembly
54     su = pym.LinSolve()(sK, f)  # Solver for linear system of equations
55     sg0 = pym.EinSum(expression="i,i->")(su, f)  # Compliance calculation
56     pym.PlotDomain(domain=domain, saveto="out/design")(sxfilt), pym.PlotIter()(sg0)  # Show design and iter history
57
58 # Perform the actual optimization, using OC
59 print("Initial g0 {0:.3e}, vol {1:.3f}".format(sg0.state, np.sum(sx.state) / (nx * ny)))
60 loop, change = 0, 1.0
61 while change > 0.01:
62     loop += 1
63     func.reset()  # Clear previous sensitivities
64     sg0.sensitivity = 1.0  # Seed the last sensitivity with a value of 1.0
65     func.sensitivity()  # Backpropagation to calculate all sensitivities
66     sx.state, change = oc_update(sx.state, sx.sensitivity)
67     func.response()  # Forward analysis (note: the first is already calculated so only done after the first update)
68     vol = np.sum(sx.state) / (nx * ny)
69     print("It {0: 3d}, g0 {1:.3e}, vol {2:.3f}, change {3:.2f}".format(loop, sg0.state, vol, change))

Gallery generated by Sphinx-Gallery