├── .github └── FUNDING.yml ├── README.md ├── figures ├── figure_mma.png └── figure_oc.png └── src └── topopt.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: # Replace with a single Patreon username 4 | open_collective: # Replace with a single Open Collective username 5 | ko_fi: # Replace with a single Ko-fi username 6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 7 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 8 | liberapay: # Replace with a single Liberapay username 9 | issuehunt: # Replace with a single IssueHunt username 10 | otechie: # Replace with a single Otechie username 11 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 12 | 13 | ko_fi: arjendeetman 14 | github: arjendeetman 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TopOpt-MMA-Python 2 | Example application of the [GCMMA-MMA-Python](https://github.com/arjendeetman/GCMMA-MMA-Python) repo in topology optimization. The original toplogy optimization code is written by [Niels Aage and Villads Egede Johansen (Technical University of Denmark)](http://www.topopt.mek.dtu.dk/Apps-and-software/Topology-optimization-codes-written-in-Python). The python code is the equivalent of the efficient 88 lines MATLAB code. The original python code can be downloaded [here](http://www.topopt.mek.dtu.dk/Apps-and-software/Topology-optimization-codes-written-in-Python). To use the modified Python code with the MMA optimizer the user needs to install the `mmapy` package with `pip install mmapy`. More information about the package can be found [here](https://github.com/arjendeetman/GCMMA-MMA-Python). 3 | 4 | ## References 5 | 6 | Aage, N., Johansen, V.E. (2013). A 165 LINE TOPOLOGY OPTIMIZATION CODE. Retrieved November 2, 2019 from 7 | http://www.topopt.mek.dtu.dk/Apps-and-software/Topology-optimization-codes-written-in-Python 8 | 9 | [Andreassen, E., Clausen, A., Schevenels, M., Lazarov, B.S., Sigmund, O. (2011). Efficient topology optimization in MATLAB 10 | using 88 lines of code. Structural and Multidisciplinary Optimization 43. 1-16. doi:10.1007/s00158-010-0594-7](https://link.springer.com/article/10.1007/s00158-010-0594-7) 11 | 12 | [Liu, K., Tovar, A. (2014). An efficient 3D topology optimization code written in Matlab. Structural and Multidisciplinary Optimization 50. 1175–1196. doi:10.1007/s00158-014-1107-x](https://doi.org/10.1007/s00158-014-1107-x) 13 | 14 | [Svanberg, K. (1987). The Method of Moving Asymptotes – A new method for structural optimization. International Journal 15 | for Numerical Methods in Engineering 24, 359-373. doi:10.1002/nme.1620240207](https://onlinelibrary.wiley.com/doi/abs/10.1002/nme.1620240207) 16 | 17 | Svanberg, K. (n.d.). MMA and GCMMA – two methods for nonlinear optimization. Retrieved August 3, 2017 from 18 | https://people.kth.se/~krille/mmagcmma.pdf 19 | -------------------------------------------------------------------------------- /figures/figure_mma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arjendeetman/TopOpt-MMA-Python/42d52844d46ad580fb7e60532411bfe0f2e9b87f/figures/figure_mma.png -------------------------------------------------------------------------------- /figures/figure_oc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arjendeetman/TopOpt-MMA-Python/42d52844d46ad580fb7e60532411bfe0f2e9b87f/figures/figure_oc.png -------------------------------------------------------------------------------- /src/topopt.py: -------------------------------------------------------------------------------- 1 | # A 165 LINE TOPOLOGY OPTIMIZATION CODE BY NIELS AAGE AND VILLADS EGEDE JOHANSEN, JANUARY 2013 2 | # MMA OPTIMIZER ADDED BY ARJEN DEETMAN, NOVEMBER 2019 3 | from __future__ import division 4 | import numpy as np 5 | from scipy.sparse import coo_matrix 6 | from scipy.sparse.linalg import spsolve 7 | from matplotlib import colors 8 | import matplotlib.pyplot as plt 9 | from mmapy import mmasub,subsolv 10 | # MAIN DRIVER 11 | def main(nelx,nely,volfrac,penal,rmin,ft,xsolv): 12 | print("Minimum compliance problem with OC") 13 | print("ndes: " + str(nelx) + " x " + str(nely)) 14 | print("volfrac: " + str(volfrac) + ", rmin: " + str(rmin) + ", penal: " + str(penal)) 15 | print("Filter method: " + ["Sensitivity based","Density based"][ft]) 16 | print("Optimizer: " + ["OC method","MMA"][xsolv]) 17 | # Max and min stiffness 18 | Emin = 1e-9 19 | Emax = 1.0 20 | # dofs: 21 | ndof = 2*(nelx+1)*(nely+1) 22 | # Allocate design variables (as array), initialize and allocate sens. 23 | n = nely*nelx 24 | x = volfrac*np.ones(nely*nelx, dtype=float) 25 | xPhys = x.copy() 26 | dc = np.zeros((nely,nelx), dtype=float) 27 | # Initialize OC 28 | if xsolv == 0: 29 | xold1 = x.copy() 30 | g = 0 # must be initialized to use the NGuyen/Paulino OC approach 31 | # Initialize MMA 32 | elif xsolv == 1: 33 | m = 1 34 | xmin = np.zeros((n,1)) 35 | xmax = np.ones((n,1)) 36 | xval = x[np.newaxis].T 37 | xold1 = xval.copy() 38 | xold2 = xval.copy() 39 | low = np.ones((n,1)) 40 | upp = np.ones((n,1)) 41 | a0 = 1.0 42 | a = np.zeros((m,1)) 43 | c = 10000*np.ones((m,1)) 44 | d = np.zeros((m,1)) 45 | move = 0.2 46 | # FE: Build the index vectors for the for coo matrix format. 47 | KE = lk() 48 | edofMat = np.zeros((nelx*nely,8),dtype=int) 49 | for elx in range(nelx): 50 | for ely in range(nely): 51 | el = ely+elx*nely 52 | n1 = (nely+1)*elx+ely 53 | n2 = (nely+1)*(elx+1)+ely 54 | edofMat[el,:] = np.array([2*n1+2, 2*n1+3, 2*n2+2, 2*n2+3,2*n2, 2*n2+1, 2*n1, 2*n1+1]) 55 | # Construct the index pointers for the coo format 56 | iK = np.kron(edofMat,np.ones((8,1))).flatten() 57 | jK = np.kron(edofMat,np.ones((1,8))).flatten() 58 | # Filter: Build (and assemble) the index+data vectors for the coo matrix format 59 | nfilter = int(nelx*nely*((2*(np.ceil(rmin)-1)+1)**2)) 60 | iH = np.zeros(nfilter) 61 | jH = np.zeros(nfilter) 62 | sH = np.zeros(nfilter) 63 | cc = 0 64 | for i in range(nelx): 65 | for j in range(nely): 66 | row = i*nely+j 67 | kk1 = int(np.maximum(i-(np.ceil(rmin)-1),0)) 68 | kk2 = int(np.minimum(i+np.ceil(rmin),nelx)) 69 | ll1 = int(np.maximum(j-(np.ceil(rmin)-1),0)) 70 | ll2 = int(np.minimum(j+np.ceil(rmin),nely)) 71 | for k in range(kk1,kk2): 72 | for l in range(ll1,ll2): 73 | col = k*nely+l 74 | fac = rmin-np.sqrt(((i-k)*(i-k)+(j-l)*(j-l))) 75 | iH[cc] = row 76 | jH[cc] = col 77 | sH[cc] = np.maximum(0.0,fac) 78 | cc = cc+1 79 | # Finalize assembly and convert to csc format 80 | H = coo_matrix((sH,(iH,jH)),shape=(nelx*nely,nelx*nely)).tocsc() 81 | Hs = H.sum(1) 82 | # BC's and support 83 | dofs = np.arange(2*(nelx+1)*(nely+1)) 84 | fixed = np.union1d(dofs[0:2*(nely+1):2], np.array([2*(nelx+1)*(nely+1)-1])) 85 | free = np.setdiff1d(dofs,fixed) 86 | # Solution and RHS vectors 87 | f = np.zeros((ndof,1)) 88 | u = np.zeros((ndof,1)) 89 | # Set load 90 | f[1,0] = -1 91 | # Set loop counter and gradient vectors 92 | loop = 0 93 | change = 1 94 | dv = np.ones(nely*nelx) 95 | dc = np.ones(nely*nelx) 96 | ce = np.ones(nely*nelx) 97 | while (change>0.001) and (loop<2000): 98 | loop = loop+1 99 | # Setup and solve FE problem 100 | sK = ((KE.flatten()[np.newaxis]).T*(Emin+(xPhys)**penal*(Emax-Emin))).flatten(order='F') 101 | K = coo_matrix((sK,(iK,jK)),shape=(ndof,ndof)).tocsc() 102 | # Remove constrained dofs from matrix 103 | K = K[free,:][:,free] 104 | # Solve system 105 | u[free,0] = spsolve(K,f[free,0]) 106 | # Objective and sensitivity 107 | ce[:] = (np.dot(u[edofMat].reshape(nelx*nely,8),KE) * u[edofMat].reshape(nelx*nely,8) ).sum(1) 108 | obj = ((Emin+xPhys**penal*(Emax-Emin))*ce ).sum() 109 | dc[:] = (-penal*xPhys**(penal-1)*(Emax-Emin))*ce 110 | dv[:] = np.ones(nely*nelx) 111 | # Sensitivity filtering: 112 | if ft == 0: 113 | dc[:] = np.asarray((H*(x*dc))[np.newaxis].T/Hs)[:,0] / np.maximum(0.001,x) 114 | elif ft == 1: 115 | dc[:] = np.asarray(H*(dc[np.newaxis].T/Hs))[:,0] 116 | dv[:] = np.asarray(H*(dv[np.newaxis].T/Hs))[:,0] 117 | # Optimality criteria 118 | if xsolv == 0: 119 | xold1[:] = x 120 | (x[:],g) = oc(nelx,nely,x,volfrac,dc,dv,g) 121 | # Method of moving asymptotes 122 | elif xsolv == 1: 123 | mu0 = 1.0 # Scale factor for objective function 124 | mu1 = 1.0 # Scale factor for volume constraint function 125 | f0val = mu0*obj 126 | df0dx = mu0*dc[np.newaxis].T 127 | fval = mu1*np.array([[xPhys.sum()/n-volfrac]]) 128 | dfdx = mu1*(dv/(n*volfrac))[np.newaxis] 129 | xval = x.copy()[np.newaxis].T 130 | xmma,ymma,zmma,lam,xsi,eta,mu,zet,s,low,upp = \ 131 | mmasub(m,n,k,xval,xmin,xmax,xold1,xold2,f0val,df0dx,fval,dfdx,low,upp,a0,a,c,d,move) 132 | xold2 = xold1.copy() 133 | xold1 = xval.copy() 134 | x = xmma.copy().flatten() 135 | # Filter design variables 136 | if ft == 0: xPhys[:] = x 137 | elif ft == 1: xPhys[:] = np.asarray(H*x[np.newaxis].T/Hs)[:,0] 138 | # Compute the change by the inf. norm 139 | change = np.linalg.norm(x.reshape(nelx*nely,1)-xold1.reshape(nelx*nely,1),np.inf) 140 | # Write iteration history to screen (req. Python 2.6 or newer) 141 | print("it.: {0} , obj.: {1:.3f} Vol.: {2:.3f}, ch.: {3:.3f}".format(loop,obj,x.sum()/n,change)) 142 | # Plot result 143 | fig,ax = plt.subplots() 144 | im = ax.imshow(-xPhys.reshape((nelx,nely)).T, cmap='gray',\ 145 | interpolation='none', norm=colors.Normalize(vmin=-1,vmax=0)) 146 | plt.show() 147 | #element stiffness matrix 148 | def lk(): 149 | E = 1 150 | nu = 0.3 151 | k = np.array([1/2-nu/6,1/8+nu/8,-1/4-nu/12,-1/8+3*nu/8,-1/4+nu/12,-1/8-nu/8,nu/6,1/8-3*nu/8]) 152 | KE = E/(1-nu**2)*np.array([ [k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7]], 153 | [k[1], k[0], k[7], k[6], k[5], k[4], k[3], k[2]], 154 | [k[2], k[7], k[0], k[5], k[6], k[3], k[4], k[1]], 155 | [k[3], k[6], k[5], k[0], k[7], k[2], k[1], k[4]], 156 | [k[4], k[5], k[6], k[7], k[0], k[1], k[2], k[3]], 157 | [k[5], k[4], k[3], k[2], k[1], k[0], k[7], k[6]], 158 | [k[6], k[3], k[4], k[1], k[2], k[7], k[0], k[5]], 159 | [k[7], k[2], k[1], k[4], k[3], k[6], k[5], k[0]] ]); 160 | return (KE) 161 | # Optimality criterion 162 | def oc(nelx,nely,x,volfrac,dc,dv,g): 163 | l1 = 0 164 | l2 = 1e9 165 | move = 0.2 166 | # reshape to perform vector operations 167 | xnew = np.zeros(nelx*nely) 168 | while (l2-l1)/(l1+l2)>1e-3: 169 | lmid = 0.5*(l2+l1) 170 | xnew[:] = np.maximum(0.0,np.maximum(x-move,np.minimum(1.0,np.minimum(x+move,x*np.sqrt(-dc/dv/lmid))))) 171 | gt = g+np.sum((dv*(xnew-x))) 172 | if gt > 0: 173 | l1 = lmid 174 | else: 175 | l2 = lmid 176 | return (xnew,gt) 177 | # The real main driver 178 | if __name__ == "__main__": 179 | # Default input parameters 180 | nelx = 150 181 | nely = 50 182 | volfrac = 0.5 183 | rmin = 1.5 184 | penal = 3.0 185 | ft = 1 # ft==0 -> sens, ft==1 -> dens 186 | xsolv = 1 # xsolv==0 -> OC, xsolv==1 -> MMA 187 | import sys 188 | if len(sys.argv)>1: nelx = int(sys.argv[1]) 189 | if len(sys.argv)>2: nely = int(sys.argv[2]) 190 | if len(sys.argv)>3: volfrac = float(sys.argv[3]) 191 | if len(sys.argv)>4: rmin = float(sys.argv[4]) 192 | if len(sys.argv)>5: penal = float(sys.argv[5]) 193 | if len(sys.argv)>6: ft = int(sys.argv[6]) 194 | if len(sys.argv)>7: xsolv = int(sys.argv[7]) 195 | main(nelx,nely,volfrac,penal,rmin,ft,xsolv) --------------------------------------------------------------------------------