This is a topology optimization program based on the entropic mirror descent algorithm described in the paper Proximal Galerkin: A structure-preserving finite element method for pointwise bound constraints by Brendan Keith and Thomas M. Surowiec. It uses design files to define the boundary conditions and other parameters for your optimization problem in an easy-to-read way. Topomax was made for my master's thesis Comparing Classical and Machine Learning Based Approaches to Topology Optimization, available on my website, and as such, it implements both the finite element method and the deep energy method. The DEM implementation is based on the paper Deep energy method in topology optimization applications by Junyan He et al., where I have heavily rewritten the source code they provided, as well as fixed some of their bugs.
Topomax depends on FEniCS, which is not available on Windows. The TopOpt repository includes a docker image which makes running the program on Windows easy, but as it is not my docker image, I can't guarantee that it will work forever. After downloading docker you can simply run
docker pull ghcr.io/johanneshaubner/topopt:latest
docker run -it -v "$(pwd):/topomax" -w /topomax ghcr.io/johanneshaubner/topopt
Then, in the docker container, you can run the program as normal:
pip install -r requirements.txt
python3 run.py designs/diffuser.json 40
This also works if you are using Linux and Mac, but there you can also install FEniCS directly by following their instalation instructions. After installing FEniCS, you can install the rest of the dependencies using the requirements file.
The program is run using run.py
. This program takes in two command line arguments; a design file and the number of finite elements along the shortest length. The folder designs
contains some design files, and you can easily make a custom design using those files as a template. If the design is path/to/design.json
, the output of the program is saved to output/design/data
. The program also takes in some optional arguments, which you can see by running run.py -h
. The produced data can be visualized with plot.py
, which automatically reads all the data files, and produces corresponding figures in output/design/figures
. You can limit what data is plotted by using some optional command line arguments which are listed when you run plot.py -h
.
The design files are written in JSON, but they are made by a Rust program. I did this so I could use Rust's rich type system to structure the data, and will therefore explain the design file format using the original Rust types. The root type is
enum Design {
Fluid(ProblemDesign<FluidParameters>),
Elasticity(ProblemDesign<ElasticityParameters>),
}
struct ProblemDesign<T> {
domain_parameters: DomainParameters,
problem_parameters: T,
}
You can either have a fluid design with fluid parameters, or you can have an elasticity design with elasticity parameters. All designs have domain parameters, which are defined as:
struct DomainParameters {
width: f64,
height: f64,
fem_step_size: f64,
dem_step_size: f64,
penalties: Vec<f32>,
volume_fraction: f64,
}
Where you specify the width and height of your domain and the volume fraction for the volume constraint. The penalties are a list of either
For a fluid problem, the fluid parameters are:
struct FluidParameters {
flows: Vec<Flow>,
viscosity: f64,
}
where a flow is defined as
struct Flow {
side: Side,
center: f64,
length: f64,
rate: f64,
}
and a side is simply
enum Side {
Left,
Right,
Top,
Bottom,
}
A flow struct represents a parabolic flow in/out of a side, with a value of rate
at its center. A positive rate represents inflow, and a negative rate represents outflow. Multiple flows can exist on the same side, and the total flow (sum of length * rate
for all flows) must be 0. For sides with no defined flow, the flow is assumed to be zero.
For an elasticity problem, the elasticity parameters are:
struct ElasticityParameters {
fixed_sides: Vec<Side>,
body_force: Option<Force>,
tractions: Option<Vec<Traction>>,
filter_radius: f64,
young_modulus: f64,
poisson_ratio: f64,
}
where a force is
struct Force {
region: CircularRegion,
value: (f64, f64),
}
struct CircularRegion {
center: (f64, f64),
radius: f64,
}
and a traction is
struct Traction {
side: Side,
center: f64,
length: f64,
value: (f64, f64),
}
The force represents a body force with a given value that acts on a circle with a given center and radius, the traction represents a traction applied to a portion of one of the boundary sides, and the fixed_sides
parameter represents the sides where the material is fixed in place. The filter radius is the radius for the Helmholtz filter.
This program uses pytest for testing, so running them is simply done by running
pytest tests
The requirements file does not include pytest, so you first need to run
pip install pytest