This class defines solutions to instances of the Problem class. They usually result from calling a topo_solver with a problem object, but can also be instantiated manually by passing a problem and a density distribution.

class Solution[source]

Solution(problem:dl4to.problem.Problem, θ:Tensor, name:str=None, enforce_θ_on_Ω_design:bool=True)

A class that defines solution objects.

Type Default Details
problem dl4to.problem.Problem The problem to which this is a solution.
θ Tensor The tensor that defines a density distribution that solves the TO problem.
name str None The name of the solution.
enforce_θ_on_Ω_design bool True Whether the density distribution should be modified such that it fulfills the density restrictions imposed by the problem object.

Methods

Solution.get_θ[source]

Solution.get_θ(binary:bool=False)

Returns the density distribution θ from the solution object. Note that you can also obtain that density via Solution.θ, however this function has the option to return a binarized version of it.

Type Default Details
binary bool False Whether the density should be binarized, i.e., thresholded at 0.5

Solution.clone[source]

Solution.clone()

Returns a deepcopy of the Solution object.

Solution.detach[source]

Solution.detach()

Returns a clone of the solution object that has a density θ which is detached from its computational graph.

Solution.detach_[source]

Solution.detach_()

Detaches θ from its computational graph. This is an in-place version of the detach() method.

Solution.plot[source]

Solution.plot(binary:bool=False, solve_pde:bool=False, normalize_σ_vm:bool=True, threshold:float=0.0, display:bool=True, file_path:str=None, camera_position:tuple=(0, 0.1, 0.12), show_design_space:bool=False, use_pyvista:bool=False, window_size:Union[tuple, list]=(800, 800), smooth_iters:int=0, show_colorbar:bool=True, show_axislabels:bool=False, show_ticklabels:bool=False, export_png:bool=False)

Renders a 3D figure that displays the density distribution and, optionally, additional ones that display the displacements and von Mises stresses for that density.

Type Default Details
binary bool False Whether the density should be binarized before plotting and before solving the PDE. Note that the PDE is solved only if solve_pde=True and problem has a PDE solver attached to it.
solve_pde bool False Whether to solve the PDE of linear elasticity and plot the absolute displacements and von Mises stresses.
normalize_σ_vm bool True Whether the von Mises stresses should be normalized with the yield stress.
threshold float 0.0 Density threshold below which voxels should be displayed as empty.
display bool True Whether the figure is displayed.
file_path str None Path where the figure is saved.
camera_position tuple (0, 0.1, 0.12) x, y, and z coordinates of the camera position.
show_design_space bool False Whether to highlight the voxels that have a design space information of -1 assigned to them.
use_pyvista bool False Whether to use pyvista for plotting. If False, then plotly is used. Pyvista generates better looking visualizations, but does not support basic features like colorbars, title display and saving.
window_size typing.Union[tuple, list] (800, 800) The size of the window that displays the plot. Only has an effect if use_pyvista=True.
smooth_iters int 0 The number of smoothing iterations for better looking visualizations. Only has an effect if use_pyvista=True.
show_colorbar bool True Determines whether a reference colorbar is displayed for the plotted voxel color values.
show_axislabels bool False Whether the 3d axes are labelled with their dimensions.
show_ticklabels bool False Whether the 3d axes ticks are displayed and labeled.
export_png bool False Whether the figure is exported and saved as a png file, in addition to the standard html format.

Solution.solve_pde[source]

Solution.solve_pde(p:float=1.0, binary:bool=False)

Solves the PDE of linear elasticity for the current solution. Returns the displacement tensor, stress tensor and the von Mises stress tensor.

Type Default Details
p float 1.0 Denotes the SIMP exponent and should usually be left at its default value of 1.
binary bool False Whether the density should be binarized before solving the PDE.

Solution.eval[source]

Solution.eval(criterion:dl4to.criteria.Criterion)

Evaluate the solution object with criterion. Only works for unsupervised criteria. Returns a torch.Tensor.

Type Default Details
criterion dl4to.criteria.Criterion The criterion for which the solution should be evaluated.

Solution.__mul__[source]

Solution.__mul__(λ:float)

Multiplication of a solution with a scalar returns a clone of the solution that has the original density distribution that is rescaled with λ. Returns a new solution object.

Type Default Details
λ float The multiplier for the density.

Examples

from dl4to.solution import Solution
from dl4to.datasets import BasicDataset

problem = BasicDataset(resolution=40).ledge()
shape = [40, 4, 8]
θ = torch.zeros(1, *shape)
solution_zeros = Solution(problem, θ)
camera_position = (0, 0.35, 0.2)
solution_zeros.plot(camera_position=camera_position,
                    display=False)

zero_density

As we can see, the density distribution $\theta$ has been modified inside of the solution object such that it is not $0$ everywhere anymore. More precisely, it has been adjusted according to the design space information that we prescribed in the problem formulation: We enforced densities of $1$ at certain voxels by setting $\Omega_\text{dirichlet}$ to $1$.

We can check that the density distribution inside of the solution object has indeed been modified and this is not just the visualization:

assert torch.any(solution_zeros.θ != 0)
assert torch.all(θ == 0)

The easiest type of solution is what we call a "trivial solution". The density distribution of a trivial solution in $1$ everywhere, where it is permitted (i.e. where $\Omega_\text{design}$ is not $0$). Together with the zero-density example above this therefore constitutes the simplest solution to a TO problem - we simply choose the thickest possible structure!

Each problem object directly comes with its own trivial solution. It can be accessed via:

trivial_solution = problem.trivial_solution

trivial_solution.plot(camera_position=camera_position,
                      display=False)

trivial_density

In order to evaluate the stresses we need to solve the partial differential equation (PDE) of linear elasticity. This library comes with its own in-built finite differences method (FDM) solver, which solves the PDE for us. We found handling with finite differences easier than with finite elements. This is attributed to the regular grid structure, which makes the FDM a suitable and intuitive approach. It is however also possible to include custom PDE solvers, e.g., learned PDE solvers - which we will discuss later.

PDE solvers are passed to problem instances:

from dl4to.pde import FDM

problem.pde_solver = FDM()

Passing PDE solvers to problems (instead of passing them to solutions or TO solvers) my seem unintuitive at first, but it comes with several advantages: First, all solution objects that are derived from this problem will also have access to the PDE solver. The same holds for all TO solver algorithms that we apply to this problem. Second, our implementation automatically constructs most of the PDE system matrix in the background when it is passed to a problem. This saves a lot of time for all future evaluations.

We can now use this FDM solver to solve the PDE. This is done via the "solve_pde" command, which returns three tensors:

  • The displacements $u$, which is a ($3\times n_x \times n_y \times n_z$)-tensor.
  • The stresses $\sigma$, which is a symmetric ($9\times n_x \times n_y \times n_z$)-tensor.
  • The von Mises stresses $\sigma_\text{vM}$, which is a ($1\times n_x \times n_y \times n_z$)-tensor, i.e., a scalar field.
u, σ, σ_vm = trivial_solution.solve_pde()

After the PDE has been solved for a solution, the displacements $u$ are stored inside the solution object and can be accessed via "trivial_solution.u". This avoids solving the same PDE several times.

In order to check if the von Mises stresses are too large, we need to compare its maximum to the yield stress $\sigma_\text{ys}$ of the material:

σ_vm.max() / problem.σ_ys
tensor(0.0198)

Since the fraction returns a value below $1$, this indicates that the structure indees holds the applied forces and does not break!

We can also visualize the spacial distribution of the (normed) displacements and von Mises stresses by passing "solve_pde=True" to the plotting function:

trivial_solution.plot(camera_position=camera_position,
                      solve_pde=True,
                      display=False)

trivial_density_theta trivial_density_u trivial_density_stress