Showcase for 3D thermal heat-sink

This example showcases the topology optimization of thermal loadcase for a heat-sink

The structure is subjected to a distributed heat load and the optimization seeks to minimize the thermal resistance. As the problem is 2-fold symmetric, only a quarter of the domain needs analysis and optimization. With post-processing the design can be mirrored to restore the complete domain (e.g. using Paraview).

This example is based on Multigrid preconditioned CG solver.

 12 import numpy as np
 13 import pymoto as pym
 14
 15 n = 128  # Resolution (# elements). Use powers of 2 for good GMG performance (e.g. 4, 8, 16, ...)
 16 nx, ny, nz = n, n, 2*n  # Domain sizes (# elements)
 17 Lx = 0.1  # Length in (m), other dimensions are deduced from `ny` and `nz`
 18 xmin = 1e-9  # Minimum density value
 19 filter_radius = 3.0  # Filter radius
 20 volfrac = 0.1  # Target volume fraction
 21
 22 if __name__ == "__main__":
 23     print(__doc__)
 24
 25     # Setup domain
 26     h = Lx/nx  # Element size
 27     domain = pym.VoxelDomain(nx, ny, nz, unitx=h, unity=h, unitz=h)  # Setup domain object
 28
 29     # BC heat sink at bottom
 30     boundary_dofs = domain.nodes[:-int(nx/2), :-int(ny/2), 0].flatten()
 31
 32     # Load vector on top section of domain
 33     eidx_load = domain.elements[:, :, int(nz/4):].flatten()
 34     nidx_load = domain.conn[eidx_load, :]
 35     f = np.zeros(domain.nnodes)
 36     np.add.at(f, nidx_load, 1.0)
 37     f *= 1 / (4*np.sum(f))  # Normalize to 1/4W unit heat input (1W for the total symmetric domain)
 38
 39     # Make design vector and fill with initial values
 40     sx = pym.Signal('x', state=np.ones(domain.nel) * volfrac)
 41
 42     # Padding elements for density filter (set densities to 0)
 43     delem = int(np.ceil(filter_radius))
 44     delem0 = delem - 1
 45     eidx_xmax = domain.elements[-delem0:, :, :].flatten()
 46     eidx_ymax = domain.elements[:, -delem0:, :].flatten()
 47     eidx_zmax = domain.elements[:, :, -delem0:].flatten()
 48     eidx_0 = np.unique(np.concatenate([eidx_xmax, eidx_ymax, eidx_zmax]))
 49
 50     # Start building the modular network
 51     with pym.Network() as func:
 52         # Set elements at the edge to zero to prevent the design 'sticking' to the boundary
 53         sx0 = pym.SetValue(indices=eidx_0, value=0.0)(sx)
 54
 55         # Density filter
 56         sxfilt = pym.FilterConv(domain, radius=filter_radius, xmax_bc=0, ymax_bc=0, zmax_bc=0)(sx0)
 57         sx_analysis = sxfilt
 58         sx_analysis.tag = 'design'
 59
 60         # SIMP material interpolation
 61         sSIMP = pym.MathExpression(f"{xmin} + {1.0 - xmin}*inp0^3")(sx_analysis)
 62
 63         # System matrix assembly module
 64         sK = pym.AssemblePoisson(domain, bc=boundary_dofs, material_property=237.)(sSIMP)
 65
 66         # Linear system solver. The linear solver can be chosen by uncommenting any of the following lines.
 67         solver = None  # Default (solver is automatically chosen based on matrix properties)
 68         # solver = pym.solvers.SolverSparsePardiso()  # Requires Intel MKL installed
 69         # solver = pym.solvers.SolverSparseCholeskyCVXOPT()  # Requires cvxopt installed
 70         # solver = pym.solvers.SolverSparseCholeskyScikit()  # Requires scikit installed
 71
 72         # Iterative solver: CG with geometric multigrid preconditioning
 73         # Set up fine level multigrid operator
 74         mg1 = pym.solvers.GeometricMultigrid(domain)
 75         mgs = [mg1] # List of multigrid levels, starting with the finest grid
 76         while True:  # Setup multiple sub-levels
 77             # Stop if any of the new dimensions is odd
 78             if any([nn % 2 != 0 for nn in mgs[-1].sub_domain.size]):
 79                 break
 80
 81             # Stop if any of the coarse dimensions is smaller than 8
 82             if any(mgs[-1].sub_domain.size < 8):
 83                 break
 84
 85             # Create a new coarser grid
 86             mgs.append(pym.solvers.GeometricMultigrid(mgs[-1].sub_domain))
 87             mgs[-2].inner_level = mgs[-1]  # Set the inner level to the next coarser grid
 88
 89         print(f"Fine grid size: {domain.nelx} x {domain.nely} x {domain.nelz}")
 90         print(f"Multigrid levels: {len(mgs)}")
 91         print(f"Coarsest grid size: {mgs[-1].sub_domain.nelx} x {mgs[-1].sub_domain.nely} x {mgs[-1].sub_domain.nelz}")
 92
 93         # Set up the solver (comment out to use the default factorization, try this to see the difference in time)
 94         solver = pym.solvers.CG(preconditioner=mg1, verbosity=0, tol=1e-5)
 95
 96         su = pym.LinSolve(hermitian=True, solver=solver)(sK, f)
 97
 98         # Output the design, deformation, and force field to a Paraview file
 99         pym.WriteToVTI(domain, saveto='out/dat.vti')(sx_analysis, su)
100
101         # Compliance calculation c = f^T u
102         scompl = pym.EinSum('i,i->')(su, f)
103         scompl.tag = 'compliance'
104
105         # MMA needs correct scaling of the objective
106         sg0 = pym.Scaling(scaling=100.0)(scompl)
107         sg0.tag = "objective"
108
109         # Calculate the volume of the domain by adding all design densities together
110         svol = pym.EinSum('i->')(sx_analysis)
111         svol.tag = 'volume'
112
113         # Volume constraint
114         sg1 = pym.Scaling(scaling=10.0, maxval=volfrac*domain.nel)(svol)
115         sg1.tag = "volume constraint"
116
117         pym.PlotIter()(sg0, sg1)  # Plot iteration history
118
119     # Do the optimization with MMA
120     # pym.minimize_mma([sx], [sg0, sg1], function=func)
121
122     # Do the optimization with OC
123     pym.minimize_oc(sx, sg0, function=func)
124
125     print("The optimization has finished!")
126     print(f"The final compliance value obtained is {scompl.state}")
127     print(f"The maximum displacement is {max(np.absolute(su.state))}")

Gallery generated by Sphinx-Gallery