PolyFEM
Loading...
Searching...
No Matches
ScalarVarForm.cpp
Go to the documentation of this file.
1#include "ScalarVarForm.hpp"
2
5
8
11
16
24
25#include <unsupported/Eigen/SparseExtra>
26
27#include <polysolve/linear/FEMSolver.hpp>
28
29#include <algorithm>
30
31namespace polyfem::varform
32{
33 namespace
34 {
35 void write_matrix_market(const json &args, const StiffnessMatrix &stiffness)
36 {
37 const std::string full_mat_path = args["output"]["data"]["full_mat"];
38 if (!full_mat_path.empty())
39 Eigen::saveMarket(stiffness, full_mat_path);
40 }
41 } // namespace
42
45 const Eigen::MatrixXd &solution,
46 const io::CheckpointMetadata &metadata) const
47 {
48 VarForm::serialize_checkpoint(writer, solution, metadata);
49 write_checkpoint_ordering(writer, "primary", space_);
50 }
51
53 const io::CheckpointReader &reader,
54 Eigen::MatrixXd &solution)
55 {
56 VarForm::deserialize_checkpoint(reader, solution);
58 reorder_checkpoint_block(reader, "primary", space_, 1, 0, solution);
59 }
60
62 {
64 space_.reset();
69 rhs_assembler_ = nullptr;
70 mass_.resize(0, 0);
71 pure_mass_.resize(0, 0);
72 avg_mass_ = 0;
73 rhs_.resize(0, 0);
74 primary_assembler_ = nullptr;
75 mass_assembler_ = nullptr;
76 pure_mass_assembler_ = nullptr;
77 t0 = 0;
78 time_steps = 0;
79 dt = 0;
80 time_integrator = nullptr;
82 }
83
84 void ScalarVarForm::init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path)
85 {
86 for (const std::string field : {"pressure_boundary", "pressure_cavity"})
87 {
88 const json::json_pointer path("/boundary_conditions/" + field);
89 if (args.contains(path) && !args.at(path).empty())
90 log_and_throw_error("{} does not support boundary_conditions.{}.", formulation, field);
91 }
92
93 VarForm::init(formulation, units, args, out_path);
94 const bool is_time_dependent = args.contains("time") && !args["time"].is_null();
95
97 assert(primary_assembler_->name() == formulation);
98 assert(primary_assembler_->is_linear());
99 assert(!primary_assembler_->is_tensor());
100 mass_assembler_ = std::make_shared<assembler::Mass>();
101 pure_mass_assembler_ = std::make_shared<assembler::HRZMass>();
102
103 if (!args.contains("preset_problem"))
104 {
105 problem = std::make_shared<assembler::GenericScalarProblem>("GenericScalar");
106 problem->clear();
107
108 json tmp;
109 tmp["is_time_dependent"] = is_time_dependent;
110 problem->set_parameters(tmp, resources_);
111
112 auto bc = args["boundary_conditions"];
113 problem->set_parameters(bc, resources_);
114 problem->set_parameters(args["initial_conditions"], resources_);
115 problem->set_parameters(args["output"], resources_);
116 }
117 else
118 {
119 problem = problem::ProblemFactory::factory().get_problem(args["preset_problem"]["type"]);
120 problem->clear();
121 problem->set_parameters(args["preset_problem"], resources_);
122 }
123
124 problem->set_units(*primary_assembler_, units);
125
126 t0 = is_time_dependent ? args["time"]["t0"].get<double>() : 0.0;
127 time_steps = is_time_dependent ? args["time"]["time_steps"].get<int>() : 0;
128 dt = is_time_dependent ? args["time"]["dt"].get<double>() : 0.0;
129 }
130
131 void ScalarVarForm::load_mesh(const mesh::Mesh &mesh, const json &args)
132 {
135 pure_mass_assembler_->set_size(mass_assembler_->size());
136
137 problem->init(mesh);
138 }
139
140 void ScalarVarForm::build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
141 {
142 assert(problem);
143 assert(primary_assembler_);
144 assert(mass_assembler_);
145 assert(pure_mass_assembler_);
146
147 Eigen::VectorXi space_disc_orders, space_disc_ordersq;
148 assign_discr_orders(args["space"], mesh, space_disc_orders, space_disc_ordersq);
149
150 if (args["space"]["use_p_ref"])
151 {
153 mesh,
154 args["space"]["advanced"]["B"],
155 args["space"]["advanced"]["h1_formula"],
156 args["space"]["discr_order"],
157 args["space"]["advanced"]["discr_order_max"],
158 stats,
159 space_disc_orders);
160
161 logger().info("min p: {} max p: {}", space_disc_orders.minCoeff(), space_disc_orders.maxCoeff());
162 }
163
165 mesh,
166 iso_parametric,
167 space_disc_orders,
168 space_disc_ordersq,
169 args["space"]["basis_type"],
170 args["space"]["poly_basis_type"],
172 /*value_dim=*/1,
173 args["space"]["advanced"]["quadrature_order"],
174 args["space"]["advanced"]["mass_quadrature_order"],
175 args["space"]["advanced"]["use_corner_quadrature"],
176 args["space"]["advanced"]["n_harmonic_samples"],
177 args["space"]["advanced"]["integral_constraints"],
178 space_,
179 boundary_);
180
182
183 problem->update_nodes(space_.space_in_node_to_node);
185
186 problem->setup_bc(
187 mesh,
189 /*fe_space_id=*/-1,
194 /*value_dim=*/1);
195 std::vector<int> unused_neumann_boundary_nodes;
196 problem->setup_bc(
197 mesh,
199 /*fe_space_id=*/-1,
203 unused_neumann_boundary_nodes,
204 /*value_dim=*/1);
205
206 problem->setup_nodal_bc(
207 mesh,
209 /*fe_space_id=*/-1,
212 problem->setup_nodal_bc(
213 mesh,
215 /*fe_space_id=*/-1,
218
219 for (const int n_id : boundary_.dirichlet_nodes)
220 {
221 const int tag = mesh.get_node_id(n_id);
222 if (problem->is_nodal_dimension_dirichlet(n_id, tag, 0))
223 boundary_.boundary_nodes.push_back(n_id);
224 }
225
227
230
231 const auto &current_bases = space_.geometry_basis_list();
232 if (args["space"]["advanced"]["count_flipped_els"])
233 stats.count_flipped_elements(mesh, current_bases);
234
235 const int n_samples = 10;
236 stats.compute_mesh_size(mesh, current_bases, n_samples, args["output"]["advanced"]["curved_mesh_size"]);
237
238 logger().info("flipped elements {}", stats.n_flipped);
239 logger().info("h: {}", stats.mesh_size);
240
241 if (space_.n_bases <= args["solver"]["advanced"]["cache_size"])
242 {
243 igl::Timer timer;
244 timer.start();
245 logger().info("Building cache...");
246 ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases);
247 mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
248 pure_mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
249 logger().info(" took {}s", timer.getElapsedTime());
250 }
251 else
252 {
256 }
257 }
258
260 {
261 json rhs_solver_params = args["solver"]["linear"];
262 if (!rhs_solver_params.contains("Pardiso"))
263 rhs_solver_params["Pardiso"] = {};
264 rhs_solver_params["Pardiso"]["mtype"] = -2;
265
266 rhs_assembler_ = std::make_shared<assembler::RhsAssembler>(
267 *primary_assembler_, *mesh_, nullptr,
271 args["space"]["advanced"]["bc_method"],
272 rhs_solver_params,
273 /*fe_space_id=*/-1);
275 }
276
278 {
279 igl::Timer timer;
280 json p_params = {};
281 p_params["formulation"] = primary_assembler_->name();
282 {
283 RowVectorNd min, max, delta;
284 mesh.bounding_box(min, max);
285 delta = (max - min) / 2. + min;
286 if (mesh.is_volume())
287 p_params["bbox_center"] = {delta(0), delta(1), delta(2)};
288 else
289 p_params["bbox_center"] = {delta(0), delta(1)};
290 }
291 problem->set_parameters(p_params, resources_);
292
293 rhs_.resize(0, 0);
294
295 timer.start();
296 logger().info("Assigning rhs...");
297
299 assert(rhs_assembler_ != nullptr);
300 rhs_assembler_->assemble(mass_assembler_->density(), rhs_);
301 rhs_ *= -1;
302
303 timings.assigning_rhs_time = timer.getElapsedTime();
304 logger().info(" took {}s", timings.assigning_rhs_time);
305 }
306
307 void ScalarVarForm::assemble_mass_mat(const mesh::Mesh &mesh, const json &args)
308 {
309 if (!problem->is_time_dependent())
310 {
311 avg_mass_ = 1;
313 return;
314 }
315
316 mass_.resize(0, 0);
317
318 igl::Timer timer;
319 timer.start();
320 logger().info("Assembling mass mat...");
321
323
324 assert(mass_.size() > 0);
325
326 avg_mass_ = 0;
327 for (int k = 0; k < mass_.outerSize(); ++k)
328 {
329 for (StiffnessMatrix::InnerIterator it(mass_, k); it; ++it)
330 {
331 assert(it.col() == k);
332 avg_mass_ += it.value();
333 }
334 }
335
336 avg_mass_ /= mass_.rows();
337 logger().info("average mass {}", avg_mass_);
338
339 if (args["solver"]["advanced"]["lump_mass_matrix"])
341
342 timer.stop();
343 timings.assembling_mass_mat_time = timer.getElapsedTime();
344 logger().info(" took {}s", timings.assembling_mass_mat_time);
345
346 stats.nn_zero = mass_.nonZeros();
347 stats.num_dofs = mass_.rows();
348 stats.mat_size = (long long)mass_.rows() * (long long)mass_.cols();
349 logger().info("sparsity: {}/{}", stats.nn_zero, stats.mat_size);
350 }
351
352 void ScalarVarForm::prepare_initial_solution(Eigen::MatrixXd &solution) const
353 {
354 if (solution.size() > 0)
355 return;
356 assert(rhs_assembler_ != nullptr);
357 if (problem->is_time_dependent())
358 rhs_assembler_->initial_solution(solution);
359 else
360 {
361 solution.resize(rhs_.size(), 1);
362 solution.setZero();
363 }
364 }
365
366 void ScalarVarForm::save_json(const Eigen::MatrixXd &solution, std::ostream &out) const
367 {
368 if (!mesh_)
369 {
370 logger().error("Load the mesh first!");
371 return;
372 }
373 if (solution.size() <= 0)
374 {
375 logger().error("Solve the problem first!");
376 return;
377 }
378
379 logger().info("Saving json...");
380 const int primary_size = space_.n_bases;
381 const Eigen::MatrixXd stats_solution =
382 solution.rows() >= primary_size
383 ? solution.topRows(primary_size).eval()
384 : solution;
385
386 nlohmann::json j;
388 args, space_.n_bases, /*n_auxiliary_bases=*/0,
389 stats_solution, *mesh_, space_.disc_orders, space_.disc_ordersq, *problem,
391 args["output"]["advanced"]["sol_at_node"], j);
392 out << j.dump(4) << std::endl;
393 }
394
396 {
397 Eigen::VectorXi output_orders = space_.disc_orders;
398 if (mesh_ && space_.disc_ordersq.size() == space_.disc_orders.size())
399 {
400 for (int e = 0; e < output_orders.size(); ++e)
401 {
402 if (mesh_->is_prism(e))
403 output_orders(e) = std::max(space_.disc_orders(e), space_.disc_ordersq(e));
404 }
405 }
406
407 return {
408 mesh_.get(),
410 output_orders,
411 &space_.polys,
414 nullptr,
415 nullptr,
418 }
419
420 io::OutStatsData ScalarVarForm::compute_errors(const Eigen::MatrixXd &solution)
421 {
422 if (!args["output"]["advanced"]["compute_error"])
423 return stats;
424
425 double tend = 0;
426 if (!args["time"].is_null())
427 tend = args["time"]["tend"];
428
430 return stats;
431 }
432
433 void ScalarVarForm::export_data(const Eigen::MatrixXd &solution) const
434 {
435 const io::OutputSpace space = output_space();
436 if (!space.mesh)
437 {
438 logger().error("Load the mesh first!");
439 return;
440 }
441 if (solution.size() <= 0)
442 {
443 logger().error("Solve the problem first!");
444 return;
445 }
446
448
449 const std::string vis_mesh_path = resolve_output_path(args["output"]["paraview"]["file_name"]);
450 const bool has_time = args.contains("time") && !args["time"].is_null();
451 double tend = has_time ? args["time"]["tend"].get<double>() : 1.0;
452 double dt = 1;
453 if (has_time)
454 dt = args["time"]["dt"];
455
456 const auto opts = export_options(space);
458 space,
459 output_field_function(solution, opts),
460 has_time,
461 tend, dt,
462 opts,
463 vis_mesh_path);
464
465 const std::string solution_path = resolve_output_path(args["output"]["data"]["solution"]);
466 if (!solution_path.empty())
467 {
468 const int primary_ndof = std::min<int>(solution.rows(), space_.n_bases);
469 const Eigen::MatrixXd primary_solution = solution.topRows(primary_ndof);
470 if (opts.reorder_output && space_.space_in_node_to_node.size() > 0)
471 {
472 const Eigen::MatrixXd nodal_solution = utils::unflatten(primary_solution, 1);
473 Eigen::MatrixXd reordered = Eigen::MatrixXd::Zero(nodal_solution.rows(), nodal_solution.cols());
474 for (int input_node = 0; input_node < space_.space_in_node_to_node.size(); ++input_node)
475 {
476 const int node = space_.space_in_node_to_node(input_node);
477 if (node >= 0 && node < nodal_solution.rows() && input_node < reordered.rows())
478 reordered.row(input_node) = nodal_solution.row(node);
479 }
480 io::write_matrix(solution_path, reordered);
481 }
482 else
483 {
484 io::write_matrix(solution_path, primary_solution);
485 }
486 }
487
488 const std::string nodes_path = resolve_output_path(args["output"]["data"]["nodes"]);
489 if (!nodes_path.empty())
490 {
491 Eigen::MatrixXd nodes = Eigen::MatrixXd::Zero(space_.n_bases, mesh_->dimension());
492 for (const basis::ElementBases &element_bases : space_.basis_list())
493 for (const basis::Basis &basis : element_bases.bases)
494 for (const auto &global : basis.global())
495 nodes.row(global.index) = global.node;
496 io::write_matrix(nodes_path, nodes);
497 }
498
499 const std::string stress_path = resolve_output_path(args["output"]["data"]["stress_mat"]);
500 const std::string mises_path = resolve_output_path(args["output"]["data"]["mises"]);
501 if ((!stress_path.empty() || !mises_path.empty()) && primary_assembler_)
502 {
503 Eigen::MatrixXd stress;
504 Eigen::VectorXd mises;
508 stress, mises);
509 if (!stress_path.empty())
510 io::write_matrix(stress_path, stress);
511 if (!mises_path.empty())
512 io::write_matrix(mises_path, mises);
513 }
514 }
515
516 std::vector<io::OutputField> ScalarVarForm::output_fields(
517 const io::OutputSample &sample,
518 const Eigen::MatrixXd &solution,
519 const io::OutputFieldOptions &options) const
520 {
521 std::vector<io::OutputField> fields;
522 if (!mesh_ || !problem || solution.size() <= 0)
523 return fields;
524
525 assert(problem->is_scalar());
526 const bool has_element_samples = sample.local_points.rows() > 0 && sample.local_points.rows() == sample.element_ids.size();
527 const int output_rows = sample.points.rows() > 0 ? sample.points.rows() : std::max<int>(sample.local_points.rows(), sample.node_ids.size());
528 const int primary_ndof = std::min<int>(solution.rows(), space_.n_bases);
529 const Eigen::MatrixXd primary_solution = solution.topRows(primary_ndof);
530
531 const auto sample_dof_field = [&](const Eigen::MatrixXd &dof_values, Eigen::MatrixXd &values, Eigen::MatrixXd *gradients = nullptr) -> bool {
532 if (dof_values.size() <= 0)
533 return false;
534
535 if (has_element_samples)
536 {
537 values.resize(sample.local_points.rows(), 1);
538 if (gradients)
539 gradients->resize(sample.local_points.rows(), mesh_->dimension());
540 for (int i = 0; i < sample.local_points.rows(); ++i)
541 {
542 const int element_id = sample.element_ids(i);
543 if (element_id < 0)
544 {
545 values(i) = 0;
546 if (gradients)
547 gradients->row(i).setZero();
548 continue;
549 }
550
551 Eigen::MatrixXd local_sol, local_grad;
554 element_id, sample.local_points.row(i), dof_values, local_sol, local_grad);
555 values(i) = local_sol(0);
556 if (gradients)
557 gradients->row(i) = local_grad;
558 }
559
560 if (output_rows > values.rows())
561 {
562 const int previous_rows = values.rows();
563 values.conservativeResize(output_rows, Eigen::NoChange);
564 values.bottomRows(output_rows - previous_rows).setZero();
565 if (gradients)
566 {
567 gradients->conservativeResize(output_rows, Eigen::NoChange);
568 gradients->bottomRows(output_rows - previous_rows).setZero();
569 }
570 }
571 return true;
572 }
573
574 if (sample.node_ids.size() > 0)
575 {
576 values.resize(sample.node_ids.size(), 1);
577 for (int i = 0; i < sample.node_ids.size(); ++i)
578 {
579 const int node_id = sample.node_ids(i);
580 if (node_id < 0 || node_id >= dof_values.rows())
581 return false;
582 values(i) = dof_values(node_id);
583 }
584 return sample.points.rows() == 0 || sample.points.rows() == values.rows();
585 }
586
587 return false;
588 };
589
590 const auto &paraview_options = args["output"]["paraview"]["options"];
591 if (has_element_samples && problem->has_exact_sol() && sample.points.rows() == output_rows)
592 {
593 Eigen::MatrixXd exact;
594 problem->exact(sample.points, sample.time, exact);
595 if (exact.rows() == output_rows)
596 {
597 if (options.export_field("exact"))
598 fields.push_back({"exact", exact, io::OutputField::Association::Point});
599 if (options.export_field("error"))
600 {
601 Eigen::MatrixXd values;
602 if (sample_dof_field(primary_solution, values))
603 fields.push_back({"error", (values - exact).rowwise().norm(), io::OutputField::Association::Point});
604 }
605 }
606 }
607
608 if ((paraview_options["nodes"] || (!options.fields.empty() && options.export_field("nodes")))
609 && has_element_samples
610 && sample.primitive_ids.size() == 0)
611 {
612 Eigen::MatrixXd dof_ids(primary_ndof, 1);
613 dof_ids.col(0).setLinSpaced(primary_ndof, 0, primary_ndof - 1);
614 Eigen::MatrixXd values;
615 if (sample_dof_field(dof_ids, values))
616 fields.push_back({"nodes", values, io::OutputField::Association::Point});
617 }
618
619 if ((paraview_options["jacobian_validity"] || (!options.fields.empty() && options.export_field("validity")))
620 && has_element_samples
621 && mesh_->dimension() == 1
622 && sample.primitive_ids.size() == 0)
623 {
624 const auto invalid_elements = utils::count_invalid(mesh_->dimension(), space_.basis_list(), space_.geometry_basis_list(), primary_solution);
625 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
626 for (int i = 0; i < sample.element_ids.size(); ++i)
627 validity(i) = std::find(invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i)) != invalid_elements.end();
628 fields.push_back({"validity", validity, io::OutputField::Association::Point});
629 }
630
631 const bool export_solution_gradient =
632 !options.fields.empty() && options.export_field("solution_gradient");
633 if (options.export_field("solution") || export_solution_gradient)
634 {
635 Eigen::MatrixXd values, gradients;
636 if (sample_dof_field(
637 solution, values,
638 export_solution_gradient ? &gradients : nullptr))
639 {
640 if (options.export_field("solution"))
641 fields.push_back({"solution", values, io::OutputField::Association::Point});
642 if (export_solution_gradient)
643 fields.push_back({"solution_gradient", gradients, io::OutputField::Association::Point});
644 }
645 }
646
647 if (paraview_options["material"] && has_element_samples)
648 {
649 const auto &params = primary_assembler_->parameters();
650 std::map<std::string, Eigen::MatrixXd> param_values;
651 for (const auto &[p, _] : params)
652 param_values[p].setZero(output_rows, 1);
653
654 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
655 const auto &density = mass_assembler_->density();
656 for (int i = 0; i < sample.local_points.rows(); ++i)
657 {
658 const int element_id = sample.element_ids(i);
659 if (element_id < 0)
660 continue;
661
662 for (const auto &[p, func] : params)
663 param_values.at(p)(i) = func(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
664 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
665 }
666
667 for (const auto &[name, values] : param_values)
668 if (options.export_field(name))
669 fields.push_back({name, values, io::OutputField::Association::Point});
670 if (options.export_field("rho"))
671 fields.push_back({"rho", rhos, io::OutputField::Association::Point});
672 }
673
674 if (paraview_options["body_ids"] && options.export_field("body_ids") && has_element_samples)
675 {
676 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
677 for (int i = 0; i < sample.element_ids.size(); ++i)
678 {
679 const int element_id = sample.element_ids(i);
680 if (element_id >= 0)
681 ids(i) = mesh_->get_body_id(element_id);
682 }
683 fields.push_back({"body_ids", ids, io::OutputField::Association::Point});
684 }
685
686 return fields;
687 }
688
689 void ScalarVarForm::build_stiffness_mat(StiffnessMatrix &stiffness)
690 {
691 igl::Timer timer;
692 timer.start();
693 logger().info("Assembling stiffness mat...");
694 assert(primary_assembler_->is_linear());
695 assert(problem->is_scalar());
696
697 primary_assembler_->assemble(mesh_->is_volume(), space_.n_bases, space_.basis_list(), space_.geometry_basis_list(), ass_vals_cache_, 0, stiffness);
698
699 timer.stop();
700 timings.assembling_stiffness_mat_time = timer.getElapsedTime();
701 logger().info(" took {}s", timings.assembling_stiffness_mat_time);
702
703 stats.nn_zero = stiffness.nonZeros();
704 stats.num_dofs = stiffness.rows();
705 stats.mat_size = (long long)stiffness.rows() * (long long)stiffness.cols();
706 logger().info("sparsity: {}/{}", stats.nn_zero, stats.mat_size);
707
708 write_matrix_market(args, stiffness);
709 }
710
711 void ScalarVarForm::solve_linear_system(
712 const std::unique_ptr<polysolve::linear::Solver> &solver,
714 Eigen::VectorXd &b,
715 const bool compute_spectrum,
716 Eigen::MatrixXd &sol)
717 {
718 assert(primary_assembler_->is_linear());
719 assert(problem->is_scalar());
720 assert(rhs_assembler_ != nullptr);
721
722 Eigen::VectorXd x;
723 stats.spectrum = dirichlet_solve(
724 *solver,
725 A,
726 b,
727 boundary_.boundary_nodes,
728 x,
729 space_.n_bases,
730 args["output"]["data"]["stiffness_mat"],
731 compute_spectrum,
732 /*is_problem_mixed=*/false,
733 /*use_avg_pressure=*/false);
734
735 sol = x;
736 solver->get_info(stats.solver_info);
737
738 const auto error = (A * x - b).norm();
739 if (error > 1e-4)
740 logger().error("Solver error: {}", error);
741 else
742 logger().debug("Solver error: {}", error);
743 }
744
745 void ScalarVarForm::solve_linear_system_with_constraints(
746 const std::unique_ptr<polysolve::linear::Solver> &solver,
748 Eigen::VectorXd &b,
749 const bool compute_spectrum,
750 const QuadratureOrders &boundary_samples,
751 const double time,
752 Eigen::MatrixXd &sol)
753 {
754 const json &periodic_conditions = args["boundary_conditions"]["periodic"];
755 const json &zero_mean = args["constraints"]["zero_mean"];
756 const bool add_zero_mean =
757 zero_mean.is_boolean()
758 ? zero_mean.get<bool>()
759 : (zero_mean.is_array()
760 && std::find(zero_mean.begin(), zero_mean.end(), 0) != zero_mean.end());
761 const bool has_global_constraints = !periodic_conditions.empty() || add_zero_mean;
762
763 if (!has_global_constraints)
764 {
765 solve_linear_system(solver, A, b, compute_spectrum, sol);
766 return;
767 }
768
769 if (!zero_mean.is_boolean() && !zero_mean.is_array())
770 log_and_throw_error("constraints.zero_mean must be a boolean or a list of FE-space IDs");
771
772 StiffnessMatrix constraint_mass = mass_;
773 if (constraint_mass.rows() != A.rows() || constraint_mass.cols() != A.cols())
774 {
775 mass_assembler_->assemble(
776 mesh_->is_volume(), space_.n_bases, space_.basis_list(), space_.geometry_basis_list(),
777 mass_ass_vals_cache_, 0, constraint_mass, true);
778 }
779 if (constraint_mass.rows() != A.rows() || constraint_mass.cols() != A.cols())
780 log_and_throw_error("Unable to assemble scalar constraint mass matrix for {} DoFs", A.rows());
781
782 std::vector<std::shared_ptr<solver::AugmentedLagrangianForm>> constraint_forms;
783 if (!boundary_.boundary_nodes.empty())
784 {
785 constraint_forms.push_back(std::make_shared<solver::BCLagrangianForm>(
786 A.rows(), boundary_.boundary_nodes, boundary_.local_boundary,
787 boundary_.local_neumann_boundary, boundary_samples, constraint_mass,
788 *rhs_assembler_, /*obstacle_ndof=*/0, problem->is_time_dependent(), time));
789 }
790
791 for (const json &condition : periodic_conditions)
792 {
793 const int fe_space = condition.value("fe_space", -1);
794 if (fe_space >= 0 && fe_space != 0)
795 continue;
796
797 const std::array<int, 2> boundary_ids = {{condition["boundary_ids"][0].get<int>(),
798 condition["boundary_ids"][1].get<int>()}};
799 constraint_forms.push_back(std::make_shared<solver::PeriodicBoundaryLagrangianForm>(
800 A.rows(), /*value_dim=*/1, *mesh_, space_.basis_list(),
801 boundary_.total_local_boundary, boundary_ids,
802 condition.value("tolerance", 1e-5)));
803 }
804
805 if (add_zero_mean)
806 {
807 const Eigen::VectorXd weights = constraint_mass * Eigen::VectorXd::Ones(A.rows());
808 const double weight_sum = weights.cwiseAbs().sum();
809 if (weight_sum <= 0)
810 log_and_throw_error("Unable to assemble a scalar zero-mean constraint");
811
812 std::vector<Eigen::Triplet<double>> entries;
813 entries.reserve(weights.size());
814 for (int dof = 0; dof < weights.size(); ++dof)
815 if (weights(dof) != 0)
816 entries.emplace_back(0, dof, weights(dof) / weight_sum);
817
818 StiffnessMatrix C(1, A.rows());
819 C.setFromTriplets(entries.begin(), entries.end());
820 constraint_forms.push_back(std::make_shared<solver::MatrixLagrangianForm>(
821 C, Eigen::MatrixXd::Zero(1, 1)));
822 }
823
824 if (constraint_forms.empty())
825 {
826 solve_linear_system(solver, A, b, compute_spectrum, sol);
827 return;
828 }
829
830 auto constraint_solver = polysolve::linear::Solver::create(args["solver"]["linear"], logger());
831 std::shared_ptr<polysolve::linear::Solver> shared_constraint_solver(std::move(constraint_solver));
832 solver::NLProblem constrained_problem(
833 A.rows(), time, {}, constraint_forms, shared_constraint_solver,
834 /*char_length=*/1, /*char_force=*/1, constraint_mass, /*dimension=*/1);
835
836 const StiffnessMatrix full_A = A;
837 const Eigen::VectorXd affine_offset =
838 constrained_problem.reduced_to_full(Eigen::VectorXd::Zero(constrained_problem.reduced_size()));
839 b = constrained_problem.full_to_reduced_grad(b - full_A * affine_offset);
840 constrained_problem.full_hessian_to_reduced_hessian(A);
841
842 Eigen::VectorXd reduced_solution;
843 stats.spectrum = dirichlet_solve(
844 *solver, A, b, {}, reduced_solution, A.rows(),
845 args["output"]["data"]["stiffness_mat"],
846 compute_spectrum,
847 /*is_problem_mixed=*/false, /*use_avg_pressure=*/false);
848 sol = constrained_problem.reduced_to_full(reduced_solution);
849 solver->get_info(stats.solver_info);
850
851 const double error = (A * reduced_solution - b).norm();
852 if (error > 1e-4)
853 logger().error("Solver error: {}", error);
854 else
855 logger().debug("Solver error: {}", error);
856 }
857
858 void ScalarVarForm::solve_static(Eigen::MatrixXd &sol, const ForwardStepCallback &post_step)
859 {
860 auto solver = polysolve::linear::Solver::create(args["solver"]["linear"], logger());
861 logger().info("{}...", solver->name());
862
863 const int gdiscr_order = mesh_->orders().size() <= 0 ? 1 : mesh_->orders().maxCoeff();
864 const QuadratureOrders boundary_samples = n_boundary_samples(space_.disc_orders.maxCoeff(), space_.disc_ordersq.maxCoeff(), gdiscr_order);
865
866 rhs_assembler_->set_bc(
867 boundary_.local_boundary, boundary_.boundary_nodes, boundary_samples,
868 (primary_assembler_->name() != "Bilaplacian") ? boundary_.local_neumann_boundary : std::vector<mesh::LocalBoundary>(), rhs_);
869
871 build_stiffness_mat(A);
872
873 Eigen::VectorXd b = rhs_;
874 solve_linear_system_with_constraints(
875 solver, A, b, args["output"]["advanced"]["spectrum"],
876 boundary_samples, /*time=*/1, sol);
877
878 // Static scalar solves do not use solver forms, but the current adjoint
879 // implementation requires them through SolveData to compute force derivatives.
880 // TODO: Remove this hack
881 solve_data_.elastic_form = std::make_shared<solver::ElasticForm>(
882 space_.n_bases, *space_.bases, space_.geometry_basis_list(),
883 *primary_assembler_, ass_vals_cache_, 0, 0, mesh_->is_volume(),
884 args["solver"]["advanced"]["jacobian_threshold"],
885 args["solver"]["advanced"]["check_inversion"],
886 args["solver"]["advanced"]["conservative_max_iter"]);
887 solve_data_.body_form = std::make_shared<solver::BodyForm>(
888 space_.ndof(), 0, boundary_.boundary_nodes, boundary_.local_boundary,
889 boundary_.local_neumann_boundary, boundary_samples, rhs_, *rhs_assembler_,
890 mass_assembler_->density(), false, false);
891 solve_data_.body_form->update_quantities(0, sol);
892 if (post_step)
893 post_step(0, sol);
894 }
895
896 void ScalarVarForm::solve_transient(Eigen::MatrixXd &sol, const ForwardStepCallback &post_step)
897 {
898 assert(problem->is_time_dependent());
899 assert(rhs_assembler_ != nullptr);
900
901 auto solver = polysolve::linear::Solver::create(args["solver"]["linear"], logger());
902 logger().info("{}...", solver->name());
903
905 args["time"]["integrator"]);
906 bdf->init(sol, Eigen::VectorXd::Zero(sol.size()), Eigen::VectorXd::Zero(sol.size()), dt);
907 time_integrator = bdf;
908 restore_checkpoint_integrator(
909 time_integrator, "/checkpoint/state/primary_integrator", dt,
910 "primary", space_, 1);
911
912 save_timestep(t0, 0, t0, dt, sol);
913 if (post_step)
914 post_step(0, sol);
915
916 Eigen::MatrixXd current_rhs = rhs_;
917
918 StiffnessMatrix stiffness;
919 build_stiffness_mat(stiffness);
920
921 const int gdiscr_order = mesh_->orders().size() <= 0 ? 1 : mesh_->orders().maxCoeff();
922 const QuadratureOrders n_b_samples = n_boundary_samples(space_.disc_orders.maxCoeff(), space_.disc_ordersq.maxCoeff(), gdiscr_order);
923 for (int t = 1; t <= time_steps; ++t)
924 {
925 const double time = t0 + t * dt;
926
927 rhs_assembler_->compute_energy_grad(
928 boundary_.local_boundary, boundary_.boundary_nodes, mass_assembler_->density(), n_b_samples,
929 boundary_.local_neumann_boundary, rhs_, time, current_rhs);
930
931 rhs_assembler_->set_bc(
932 boundary_.local_boundary, boundary_.boundary_nodes, n_b_samples, boundary_.local_neumann_boundary, current_rhs, sol, time);
933
934 StiffnessMatrix A = mass_ / bdf->beta_dt() + stiffness;
935 Eigen::VectorXd b = (mass_ * bdf->weighted_sum_x_prevs()) / bdf->beta_dt();
936 for (int i : boundary_.boundary_nodes)
937 b[i] = 0;
938 b += current_rhs;
939
940 solve_linear_system_with_constraints(
941 solver, A, b,
942 args["output"]["advanced"]["spectrum"].get<bool>() && t == time_steps,
943 n_b_samples, time, sol);
944 if (post_step)
945 post_step(t, sol);
946
947 bdf->update_quantities(sol);
948 save_timestep(time, t, t0, dt, sol);
949 save_step_state(t0, dt, t, sol, time_integrator.get());
950
951 logger().info("{}/{} t={}", t, time_steps, time);
952 notify_time_step(t, time_steps, t0, dt);
953 }
954 }
955
956 void ScalarVarForm::solve_problem(
957 Eigen::MatrixXd &sol,
958 const InitialConditionOverride *initial_condition_override,
959 const ForwardStepCallback &post_step)
960 {
961 assert(
962 (!initial_condition_override
963 || (initial_condition_override->velocity.size() == 0
964 && initial_condition_override->acceleration.size() == 0))
965 && "Scalar formulations do not accept initial velocity or acceleration overrides");
966
967 stats.spectrum.setZero();
968
969 igl::Timer timer;
970 timer.start();
971 logger().info("Solving {}", primary_assembler_->name());
972
973 {
974 POLYFEM_SCOPED_TIMER("Setup RHS");
975
976 if (initial_condition_override && initial_condition_override->solution.size() != 0)
977 {
978 sol = initial_condition_override->solution;
979 assert(
980 sol.rows() == space_.ndof() && sol.cols() == 1
981 && "Scalar initial solution override must match the simulation DOFs and have one column");
982 }
983 else if (sol.size() <= 0)
984 prepare_initial_solution(sol);
985
986 if (sol.cols() > 1)
987 sol.conservativeResize(Eigen::NoChange, 1);
988 }
989
990 time_integrator = nullptr;
991 if (problem->is_time_dependent())
992 solve_transient(sol, post_step);
993 else
994 solve_static(sol, post_step);
995
996 timer.stop();
997 timings.solving_time = timer.getElapsedTime();
998 logger().info(" took {}s", timings.solving_time);
999 }
1000} // namespace polyfem::varform
std::vector< Eigen::Triplet< double > > entries
std::vector< std::pair< int, double > > weights
int x
std::array< Matrix< int, 3, 3 >, 3 > space_
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
static std::shared_ptr< Assembler > make_assembler(const std::string &formulation)
void init(const bool is_volume, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const bool is_mass=false)
computes the basis evaluation and geometric mapping for each of the given ElementBases in bases initi...
void init_empty(const bool is_mass=false)
initialize an empty cache.
Represents one basis function and its gradient.
Definition Basis.hpp:44
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
static void interpolate_at_local_vals(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const int el_index, const Eigen::MatrixXd &local_pts, const Eigen::MatrixXd &fun, Eigen::MatrixXd &result, Eigen::MatrixXd &result_grad)
interpolate solution and gradient at element (calls interpolate_at_local_vals with sol)
static void compute_stress_at_quadrature_points(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const assembler::Assembler &assembler, const Eigen::MatrixXd &fun, const double t, Eigen::MatrixXd &result, Eigen::VectorXd &von_mises)
compute von mises stress at quadrature points for the function fun, also compute the interpolated fun...
void export_data(const OutputSpace &space, const OutputFieldFunction &output_fields, const bool is_time_dependent, const double tend_in, const double dt, const ExportOptions &opts, const std::string &vis_mesh_path) const
exports everytihng, txt, vtu, etc
Definition OutData.cpp:2103
double assigning_rhs_time
time to computing the rhs
double assembling_mass_mat_time
time to assembly mass
all stats from polyfem
int n_flipped
number of flipped elements, compute only when using count_flipped_els (false by default)
void count_flipped_elements(const polyfem::mesh::Mesh &mesh, const std::vector< polyfem::basis::ElementBases > &gbases)
counts the number of flipped elements
Definition OutData.cpp:2834
void compute_errors(const int n_bases, const std::vector< polyfem::basis::ElementBases > &bases, const std::vector< polyfem::basis::ElementBases > &gbases, const polyfem::mesh::Mesh &mesh, const assembler::Problem &problem, const double tend, const Eigen::MatrixXd &sol)
compute errors
Definition OutData.cpp:2879
void compute_mesh_size(const polyfem::mesh::Mesh &mesh_in, const std::vector< polyfem::basis::ElementBases > &bases_in, const int n_samples, const bool use_curved_mesh_size)
computes the mesh size, it samples every edges n_samples times uses curved_mesh_size (false by defaul...
Definition OutData.cpp:2751
long long nn_zero
non zeros and sytem matrix size num dof is the total dof in the system
double mesh_size
max edge lenght
void save_json(const nlohmann::json &args, const int n_bases, const int n_pressure_bases, const Eigen::MatrixXd &sol, const mesh::Mesh &mesh, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const assembler::Problem &problem, const OutRuntimeData &runtime, const std::string &formulation, const bool isoparametric, const int sol_at_node_id, nlohmann::json &j) const
saves the output statistic to a json object
Definition OutData.cpp:3122
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:50
virtual void bounding_box(RowVectorNd &min, RowVectorNd &max) const =0
computes the bbox of the mesh
virtual bool is_volume() const =0
checks if mesh is volume
void update_nodes(const Eigen::VectorXi &in_node_to_node)
Update the node ids to reorder them.
Definition Mesh.cpp:346
virtual int get_node_id(const int node_id) const
Get the boundary selection of a node.
Definition Mesh.hpp:475
static const ProblemFactory & factory()
std::shared_ptr< assembler::Problem > get_problem(const std::string &problem) const
static void p_refine(const mesh::Mesh &mesh, const double B, const bool h1_formula, const int base_p, const int discr_order_max, io::OutStatsData &stats, Eigen::VectorXi &disc_orders)
compute a priori prefinement
Definition APriori.cpp:242
virtual TVector full_to_reduced_grad(const TVector &full) const
virtual TVector reduced_to_full(const TVector &reduced) const
void full_hessian_to_reduced_hessian(StiffnessMatrix &hessian) const
class to store time stepping data
Definition SolveData.hpp:56
std::shared_ptr< assembler::RhsAssembler > rhs_assembler
static std::shared_ptr< BDF > construct_bdf_integrator(const json &params, DynamicOrder dynamic_order=DynamicOrder::Second)
Construct a BDF integrator for algorithms using BDF-specific operations.
const std::vector< basis::ElementBases > & geometry_basis_list() const
Definition FESpace.hpp:115
Eigen::VectorXi disc_orders
Primary polynomial degree for each mesh element.
Definition FESpace.hpp:71
Eigen::VectorXi disc_ordersq
Secondary polynomial degree for anisotropic bases, e.g. prisms.
Definition FESpace.hpp:74
int n_bases
Number of globally indexed scalar basis functions in the space.
Definition FESpace.hpp:65
Eigen::VectorXi space_in_node_to_node
Definition FESpace.hpp:91
std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > polys_3d
Physical vertices and face connectivity for 3D polyhedral elements.
Definition FESpace.hpp:83
std::map< int, Eigen::MatrixXd > polys
Physical boundary samples for 2D polygonal elements.
Definition FESpace.hpp:80
const std::vector< basis::ElementBases > & basis_list() const
Definition FESpace.hpp:109
bool is_iso_parametric() const
Definition FESpace.hpp:104
void save_json(const Eigen::MatrixXd &solution, std::ostream &out) const override
Save the solution to a JSON file, for output purposes.
std::shared_ptr< assembler::HRZMass > pure_mass_assembler_
std::shared_ptr< time_integrator::ImplicitTimeIntegrator > time_integrator
void serialize_checkpoint(io::CheckpointWriter &writer, const Eigen::MatrixXd &solution, const io::CheckpointMetadata &metadata) const override
std::shared_ptr< assembler::Mass > mass_assembler_
io::OutStatsData compute_errors(const Eigen::MatrixXd &solution) override
Get the error statistics of the variational formulation, for output purposes.
void deserialize_checkpoint(const io::CheckpointReader &reader, Eigen::MatrixXd &solution) override
std::string name() const override
Get the name of the variational formulation.
void build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args) override
assembler::AssemblyValsCache pure_mass_ass_vals_cache_
assembler::AssemblyValsCache mass_ass_vals_cache_
assembler::AssemblyValsCache ass_vals_cache_
void export_data(const Eigen::MatrixXd &solution) const override
void assemble_rhs(const mesh::Mesh &mesh) override
std::vector< io::OutputField > output_fields(const io::OutputSample &sample, const Eigen::MatrixXd &solution, const io::OutputFieldOptions &options) const override
Get the output fields of the variational formulation, for output purposes.
void assemble_mass_mat(const mesh::Mesh &mesh, const json &args) override
void prepare_initial_solution(Eigen::MatrixXd &solution) const
std::shared_ptr< assembler::Assembler > primary_assembler_
std::shared_ptr< assembler::RhsAssembler > rhs_assembler_
void init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path) override
Initialize the variational formulation with the given parameters.
void load_mesh(const mesh::Mesh &mesh, const json &args) override
io::OutputSpace output_space() const override
Get the output space of the variational formulation, for output purposes.
void reorder_checkpoint_block(const io::CheckpointReader &reader, const std::string &name, const FESpace &space, int block_size, int row_offset, Eigen::MatrixXd &value) const
Definition VarForm.cpp:981
static void rebuild_node_positions(const std::vector< basis::ElementBases > &bases, const std::vector< int > &node_ids, std::vector< RowVectorNd > &positions)
Definition VarForm.cpp:1125
std::shared_ptr< assembler::Problem > problem
current problem, it contains rhs and bc
Definition VarForm.hpp:263
void write_checkpoint_ordering(io::CheckpointWriter &writer, const std::string &name, const FESpace &space) const
Definition VarForm.cpp:938
virtual void deserialize_checkpoint(const io::CheckpointReader &reader, Eigen::MatrixXd &solution)
Definition VarForm.cpp:905
std::unique_ptr< mesh::Mesh > mesh_
Definition VarForm.hpp:275
void assign_discr_orders(const json &space_args, const mesh::Mesh &mesh, Eigen::VectorXi &disc_orders, Eigen::VectorXi &disc_ordersq)
Definition VarForm.cpp:684
io::OutStatsData stats
Definition VarForm.hpp:267
io::OutGeometryData::ExportOptions export_options(const io::OutputSpace &space) const
Definition VarForm.cpp:835
io::OutGeometryData output_geometry_
Definition VarForm.hpp:279
io::OutputFieldFunction output_field_function(const Eigen::MatrixXd &solution, const io::OutGeometryData::ExportOptions &opts) const
Definition VarForm.cpp:844
virtual void serialize_checkpoint(io::CheckpointWriter &writer, const Eigen::MatrixXd &solution, const io::CheckpointMetadata &metadata) const
Definition VarForm.cpp:897
void build_fe_space(mesh::Mesh &mesh, const bool iso_parametric, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const std::string &basis_type, const std::string &poly_basis_type, const assembler::Assembler &space_assembler, const int value_dim, const int quadrature_order, const int mass_quadrature_order, const bool use_corner_quadrature, const int n_harmonic_samples, const int integral_constraints, FESpace &space, VarFormBoundaryState &boundary, std::shared_ptr< GeometryMapping > geometry=nullptr)
Definition VarForm.cpp:274
std::string resolve_output_path(const std::string &path) const
Definition VarForm.cpp:1116
void ensure_output_sampler() const
Definition VarForm.cpp:821
virtual void init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path)
Initialize the variational formulation with the given parameters.
Definition VarForm.cpp:208
void validate_checkpoint_solution(const Eigen::MatrixXd &solution, int expected_rows) const
Definition VarForm.cpp:1037
io::OutRuntimeData timings
runtime statistics
Definition VarForm.hpp:270
const io::ResourceIO & resources_
Definition VarForm.hpp:285
void set_materials(assembler::Assembler &assembler, const int size) const
Definition VarForm.cpp:806
virtual void reset()=0
Definition VarForm.cpp:194
bool write_matrix(const std::string &path, const Mat &mat)
Writes a matrix to a file. Determines the file format based on the path's extension.
Definition MatrixIO.cpp:43
Eigen::SparseMatrix< double > lump_matrix(const Eigen::SparseMatrix< double > &M)
Lump each row of a matrix into the diagonal.
Eigen::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
std::vector< int > count_invalid(const int dim, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXd &u, const unsigned max_iter)
Definition Jacobian.cpp:122
bool write_matrix_market(const json &args, const StiffnessMatrix &stiffness)
std::function< void(int step, const Eigen::MatrixXd &solution)> ForwardStepCallback
Definition VarForm.hpp:55
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
std::array< int, 2 > QuadratureOrders
Definition Types.hpp:19
nlohmann::json json
Definition Common.hpp:9
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
Definition Types.hpp:13
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
Definition Types.hpp:24
bool export_field(const std::string &field) const
Definition OutData.cpp:56
std::vector< std::string > fields
Eigen::VectorXi node_ids
Eigen::VectorXi primitive_ids
Eigen::VectorXi element_ids
Eigen::MatrixXd local_points
const mesh::Mesh * mesh
std::vector< RowVectorNd > neumann_nodes_position
Definition FESpace.hpp:163
std::vector< mesh::LocalBoundary > local_boundary
Definition FESpace.hpp:155
std::vector< mesh::LocalBoundary > local_neumann_boundary
Definition FESpace.hpp:156
std::vector< mesh::LocalBoundary > total_local_boundary
Definition FESpace.hpp:154
std::vector< RowVectorNd > dirichlet_nodes_position
Definition FESpace.hpp:161