PolyFEM
Loading...
Searching...
No Matches
InflatedPeriodicShapeVariableToSimulation.cpp
Go to the documentation of this file.
2
8
9#include <algorithm>
10#include <filesystem>
11#include <fstream>
12
13namespace polyfem::solver
14{
16 std::vector<std::shared_ptr<varform::DifferentiableVarForm>> varforms,
17 std::vector<std::shared_ptr<DiffCache>> diff_caches,
18 CompositeParametrization parametrization, const json &settings, const io::ResourceIO &resources)
19 : varforms_(std::move(varforms)),
20 diff_caches_(std::move(diff_caches)),
21 parametrization_(std::move(parametrization)),
22 dimension_(varforms_[0]->get_mesh().dimension()),
23 meshing_(settings.at("meshing"))
24 {
25 for (const auto &varform : varforms_)
26 {
27 if (varform->get_mesh().dimension() != dimension_ || !varform->is_homogenization())
28 {
29 log_and_throw_adjoint_error("Inflated periodic shape requires homogenization states of the same dimension.");
30 }
31
32 const Eigen::MatrixXd offsets = varform->periodic_tile_offsets();
33 if (offsets.cols() != dimension_ || Eigen::FullPivLU<Eigen::MatrixXd>(offsets).rank() != dimension_)
34 {
35 log_and_throw_adjoint_error("Inflated periodic shape requires periodicity in every direction.");
36 }
37 }
38
39 const auto parameters = settings.at("initial").get<std::vector<double>>();
40 initial_ = Eigen::Map<const Eigen::VectorXd>(parameters.data(), parameters.size());
41
42 // The 3D inflator uses reflection to obtain matching periodic boundary meshes.
43 const std::string symmetry = settings.at("symmetry").get<std::string>();
44 if (dimension_ == 2 && (symmetry == "auto" || symmetry == "doubly_periodic"))
45 {
46 options_.cell_type = inflator::CellType::Periodic2D;
47 }
48 else if (dimension_ == 2 && symmetry == "orthotropic")
49 {
50 options_.cell_type = inflator::CellType::Orthotropic2D;
51 }
52 else if (dimension_ == 3 && (symmetry == "auto" || symmetry == "orthotropic"))
53 {
54 options_.cell_type = inflator::CellType::Orthotropic3D;
55 }
56 else
57 {
58 log_and_throw_adjoint_error("Unsupported inflator symmetry {} in {}D.", symmetry, dimension_);
59 }
60 options_.tiles = settings.at("tiles").get<std::size_t>();
61 options_.graph_radius = settings.at("graph_radius").get<std::size_t>();
62 options_.contour_resolution = meshing_.at("marchingSquaresGridSize").get<std::size_t>();
63 options_.force_contour_resolution = meshing_.at("forceMSGridSize").get<bool>();
64 options_.max_triangle_area = meshing_.at("maxArea").get<double>();
65 options_.facet_size = meshing_.at("facetSize").get<double>();
66 options_.facet_distance = meshing_.at("facetDistance").get<double>();
67 options_.cell_size = meshing_.at("cellSize").get<double>();
68 options_.edge_size = meshing_.at("edgeSize").get<double>();
69 wire_ = resources.materialize(settings.at("wire").get<std::string>());
70 work_directory_ = varforms_[0]->output_file_path(settings.at("work_directory"));
71
72 std::filesystem::create_directories(work_directory_);
73 }
74
76 {
77 return std::any_of(varforms_.begin(), varforms_.end(), [&](const auto &varform) { return varform.get() == &target; });
78 }
79
80 void InflatedPeriodicShapeVariableToSimulation::update(const Eigen::VectorXd &variables)
81 {
82 // Layout: [cell width, graph position DOFs..., vertex radii..., joint blending...].
83 const Eigen::VectorXd parameters = parametrization_.eval(variables);
84 if (parameters.size() != initial_.size() || !parameters.allFinite() || parameters(0) <= 0)
85 {
86 log_and_throw_adjoint_error("Invalid inflated periodic shape parameters.");
87 }
88 if (parameters.size() == last_parameters_.size() && parameters == last_parameters_)
89 {
90 return;
91 }
92
93 const auto directory = std::filesystem::path(work_directory_) / std::to_string(generation_++);
94 if (!std::filesystem::create_directory(directory))
95 {
96 log_and_throw_adjoint_error("Inflator generation directory already exists: {}", directory.string());
97 }
98
99 // Debugging code
100 // const std::string options_path = (directory / "meshing.json").string();
101 // std::ofstream(options_path) << meshing_.dump(4);
102 // std::ofstream(directory / "parameters.json") << json(std::vector<double>(parameters.data(), parameters.data() + parameters.size())).dump(4);
103
104 // Microstructure is represented as a graph composed of joints, each with its own radius.
105 // Due to historical reason, we use an external inflator program to convert between graph
106 // and periodic mesh. This program also computes the chain rule term for first derivative.
107 const inflator::Mesh mesh = inflator::inflate(wire_, options_, parameters.data() + 1, parameters.size() - 1);
108
109 // Layouts: vertices[vertex * dim + axis], elements[cell * (dim + 1) + corner].
110 // Elements are triangles in 2D and tetrahedra in 3D. Scale each length-2 inflator cell
111 // to physical side lengths [width, 1, ...]; tiling multiplies these lengths by tiles.
112 Eigen::MatrixXd vertices(mesh.vertices.size() / dimension_, dimension_);
113 Eigen::MatrixXi cells(mesh.elements.size() / (dimension_ + 1), dimension_ + 1);
114 for (int vertex = 0; vertex < vertices.rows(); ++vertex)
115 {
116 for (int axis = 0; axis < dimension_; ++axis)
117 {
118 const double cell_length = axis == 0 ? parameters(0) : 1.0;
119 vertices(vertex, axis) = mesh.vertices[vertex * dimension_ + axis] * cell_length / 2;
120 }
121 }
122 for (int cell = 0; cell < cells.rows(); ++cell)
123 {
124 for (int corner = 0; corner < cells.cols(); ++corner)
125 {
126 cells(cell, corner) = mesh.elements[cell * (dimension_ + 1) + corner];
127 }
128 }
129
130 // Jacobian rows are [width, graph parameters...]; columns are interleaved vertex coordinates.
131 shape_jacobian_.setZero(parameters.size(), vertices.size());
132 for (int vertex = 0; vertex < vertices.rows(); ++vertex)
133 {
134 shape_jacobian_(0, dimension_ * vertex) = vertices(vertex, 0) / parameters(0);
135 }
136
137 // Velocities use [graph parameter][vertex][axis] order: normal speed times unit normal,
138 // with zero interior entries.
139 for (int parameter = 1; parameter < parameters.size(); ++parameter)
140 {
141 const std::size_t parameter_offset = (parameter - 1) * mesh.vertices.size();
142 for (int vertex = 0; vertex < vertices.rows(); ++vertex)
143 {
144 for (int axis = 0; axis < dimension_; ++axis)
145 {
146 const Eigen::Index coordinate = dimension_ * vertex + axis;
147 const double velocity = mesh.boundary_shape_velocities[parameter_offset + coordinate];
148 const double cell_length = axis == 0 ? parameters(0) : 1.0;
149 shape_jacobian_(parameter, coordinate) = velocity * cell_length / 2;
150 }
151 }
152 }
153 if (!vertices.allFinite() || !shape_jacobian_.allFinite())
154 {
155 log_and_throw_adjoint_error("Non-finite inflator geometry or shape velocities.");
156 }
157
158 // Regeneration can change vertex count and connectivity, requiring new periodic pairings.
159 periodic_map_ = std::make_unique<PeriodicMeshToMesh>(vertices);
160 periodic_parameters_ = periodic_map_->inverse_eval(utils::flatten(vertices));
161 periodic_jacobian_.setZero(parameters.size(), periodic_parameters_.size());
162 // Layout: [unique normalized vertex coordinates, cell matrix entries].
163 // Divide physical velocities by tiled cell lengths and assign each shared coordinate once.
164 std::vector<bool> visited(periodic_map_->n_periodic_dof(), false);
165 for (int vertex = 0; vertex < vertices.rows(); ++vertex)
166 {
167 const int representative = periodic_map_->full_to_periodic(vertex);
168 if (visited[representative])
169 {
170 continue;
171 }
172
173 visited[representative] = true;
174 for (int axis = 0; axis < dimension_; ++axis)
175 {
176 const double tiled_length = options_.tiles * (axis == 0 ? parameters(0) : 1.0);
177 const auto physical_derivatives = shape_jacobian_.block(1, dimension_ * vertex + axis, parameters.size() - 1, 1);
178 auto periodic_derivatives = periodic_jacobian_.block(1, dimension_ * representative + axis, parameters.size() - 1, 1);
179 periodic_derivatives = physical_derivatives / tiled_length;
180 }
181 }
182 // Width changes only the cell matrix in periodic coordinates: cell_xx = tiles * width.
183 const Eigen::Index cell_matrix_offset = periodic_parameters_.size() - dimension_ * dimension_;
184 periodic_jacobian_(0, cell_matrix_offset) = options_.tiles;
185
186 // File-based replace_mesh requires this MSH round-trip of the in-memory library output.
187 const std::string mesh_path = (directory / "mesh.msh").string();
188 const std::vector<int> body_ids(cells.rows(), 0);
189 const bool is_volume = dimension_ == 3;
190 io::MshWriter::write(mesh_path, vertices, cells, body_ids, is_volume, /*binary=*/true);
191 for (int state = 0; state < varforms_.size(); ++state)
192 {
193 varforms_[state]->replace_mesh(mesh_path);
194 // Clear mesh-dependent solutions and derivatives in place; objectives retain shared references to this cache.
195 *diff_caches_[state] = DiffCache();
196 }
197
198 last_parameters_ = parameters;
199 adjoint_logger().info("Inflated mesh generation {}: {} vertices, {} elements", generation_ - 1, vertices.rows(), cells.rows());
200 }
201
202 void InflatedPeriodicShapeVariableToSimulation::update_state_variables(const Eigen::VectorXd &variables, Eigen::VectorXd &state_variables) const
203 {
204 log_and_throw_adjoint_error("Generated meshes do not support fixed-connectivity SLIM updates.");
205 }
206
207 Eigen::VectorXd InflatedPeriodicShapeVariableToSimulation::compute_adjoint_term(const Eigen::VectorXd &variables) const
208 {
209 Eigen::VectorXd result = Eigen::VectorXd::Zero(initial_.size());
210 for (int state = 0; state < varforms_.size(); ++state)
211 {
212 Eigen::VectorXd term;
214 *diff_caches_[state],
217 diff_caches_[state]->u(0),
218 get_adjoint_mat(*varforms_[state], *diff_caches_[state], 0),
219 term);
220 // Chain the FEM adjoint through the inflator's geometric shape velocities.
221 result += periodic_jacobian_ * term;
222 }
223
224 return parametrization_.apply_jacobian(result, variables);
225 }
226
231
236
237 Eigen::VectorXd InflatedPeriodicShapeVariableToSimulation::apply_parametrization_jacobian(const Eigen::VectorXd &term, const Eigen::VectorXd &variables) const
238 {
239 return parametrization_.apply_jacobian(shape_jacobian_ * term, variables);
240 }
241} // namespace polyfem::solver
Storage for additional data required by differntial code.
Definition DiffCache.hpp:22
static void write(const std::string &path, const mesh::Mesh &mesh, const bool binary)
saves the mesh
Definition MshWriter.cpp:7
Read-only interface to a hierarchical collection of simulation resources.
virtual std::filesystem::path materialize(const std::string &path) const =0
Eigen::VectorXd apply_jacobian(const Eigen::VectorXd &grad_full, const Eigen::VectorXd &x) const override
Apply jacobian for chain rule.
Eigen::VectorXd inverse_eval(const Eigen::VectorXd &y) const override
Eval x = f^-1 (y).
int inverse_size(int y_size) const override
Compute DOF of x given DOF of y.
Eigen::VectorXd eval(const Eigen::VectorXd &x) const override
Eval y = f(x).
Eigen::MatrixXd shape_jacobian_
Rows are design parameters; columns are interleaved physical vertex coordinates.
Eigen::VectorXd inverse_eval() const override
Compute optimization variables from forward simulation varform::DifferentiableVarForm.
Eigen::MatrixXd periodic_jacobian_
Rows are design parameters; columns are periodic coordinates followed by the cell matrix.
InflatedPeriodicShapeVariableToSimulation(std::vector< std::shared_ptr< varform::DifferentiableVarForm > > varforms, std::vector< std::shared_ptr< DiffCache > > diff_caches, CompositeParametrization parametrization, const json &settings, const io::ResourceIO &resources)
void update(const Eigen::VectorXd &variables) override
Update forward simulation varforms from optimization variables.
Eigen::VectorXd apply_parametrization_jacobian(const Eigen::VectorXd &term, const Eigen::VectorXd &variables) const override
Apply parametrization jacobian to compute the gradient w.r.t.
std::vector< std::shared_ptr< varform::DifferentiableVarForm > > varforms_
void update_state_variables(const Eigen::VectorXd &variables, Eigen::VectorXd &state_variables) const override
Update varform variables from optimization variables.
Eigen::VectorXd compute_adjoint_term(const Eigen::VectorXd &variables) const override
Compute adjoint contribution of objective gradient.
bool affects_varform(const varform::DifferentiableVarForm &target) const override
Return true if current var2sim maps to target varform.
Optimization-facing interface implemented by differentiated VarForm adapters.
void dJ_periodic_shape_adjoint_term(const varform::DifferentiableVarForm &varform, const DiffCache &diff_cache, const PeriodicMeshToMesh &periodic_mesh_map, const Eigen::VectorXd &periodic_mesh_representation, const Eigen::MatrixXd &sol, const Eigen::MatrixXd &adjoint, Eigen::VectorXd &one_form)
Eigen::VectorXd flatten(const Eigen::MatrixXd &X)
Flatten rowwises.
spdlog::logger & adjoint_logger()
Retrieves the current logger for adjoint.
Definition Logger.cpp:30
nlohmann::json json
Definition Common.hpp:9
void log_and_throw_adjoint_error(const std::string &msg)
Definition Logger.cpp:79
Eigen::MatrixXd get_adjoint_mat(const varform::DifferentiableVarForm &varform, const DiffCache &diff_cache, int type)
Get adjoint parameter nu or p.