PolyFEM
Loading...
Searching...
No Matches
ThermoElasticVarForm.cpp
Go to the documentation of this file.
2
7
10
12
14
25
27
33
34#include <igl/Timer.h>
35
36#include <polysolve/linear/Solver.hpp>
37#include <polysolve/nonlinear/Solver.hpp>
38
39#include <algorithm>
40#include <cassert>
41#include <limits>
42#include <map>
43#include <vector>
44
45namespace polyfem::varform
46{
47 namespace
48 {
49 json first_material(const json &materials)
50 {
51 return materials.is_array() ? materials.front() : materials;
52 }
53
54 void disable_newton_psd_projection(json &solver_params)
55 {
56 const auto disable_for_newton = [](json &params) {
57 if (!params.contains("Newton") || params["Newton"].is_null())
58 params["Newton"] = json::object();
59 params["Newton"]["use_psd_projection"] = false;
60 };
61
62 if (solver_params.contains("solver") && solver_params["solver"].is_array())
63 {
64 for (json &strategy : solver_params["solver"])
65 {
66 const std::string type = strategy.value("type", "");
67 if (type == "Newton" || type == "SparseNewton" || type == "sparse_newton"
68 || type == "DenseNewton" || type == "dense_newton")
69 disable_for_newton(strategy);
70 }
71 }
72 else
73 {
74 disable_for_newton(solver_params);
75 }
76 }
77
78 void assert_same_space_ids(
79 const json &materials,
80 const int displacement_space_id,
81 const int temperature_space_id)
82 {
83 for (const json &material : utils::json_as_array(materials))
84 {
85 if (material.at("displacement_space_id").get<int>() != displacement_space_id
86 || material.at("temperature_space_id").get<int>() != temperature_space_id)
87 {
88 log_and_throw_error("All ThermoElasticity materials must use the same FE space ids.");
89 }
90 }
91 }
92
93 json elastic_material_from_thermo_material(const json &material)
94 {
95 if (!material.contains("elastic_material") || !material["elastic_material"].is_object())
96 log_and_throw_error("ThermoElasticity requires elastic_material to be an elastic material object.");
97
98 json elastic_material = material["elastic_material"];
99 const std::string type = elastic_material.value("type", "");
101 log_and_throw_error("ThermoElasticity elastic_material must be an elastic material, got '{}'.", type);
102
103 if (material.contains("id"))
104 elastic_material["id"] = material["id"];
105 if (material.contains("rho"))
106 elastic_material["rho"] = material["rho"];
107
108 return elastic_material;
109 }
110
111 json solver_params_for_residual_mode(const json &solver_params, const bool is_residual)
112 {
113 json params = solver_params;
114 if (is_residual)
115 disable_newton_psd_projection(params);
116 return params;
117 }
118
119 std::string elastic_formulation_from_thermo_materials(const json &materials)
120 {
121 std::string formulation;
122 for (const json &material : utils::json_as_array(materials))
123 {
124 const json elastic_material = elastic_material_from_thermo_material(material);
125 const std::string type = elastic_material["type"];
126 if (formulation.empty())
127 formulation = type;
128 else if (formulation != type)
129 formulation = "MultiModels";
130 }
131
132 return formulation;
133 }
134
135 StiffnessMatrix block_diag(const StiffnessMatrix &a, const StiffnessMatrix &b)
136 {
137 std::vector<Eigen::Triplet<double>> entries;
138 entries.reserve(a.nonZeros() + b.nonZeros());
139
140 for (int k = 0; k < a.outerSize(); ++k)
141 for (StiffnessMatrix::InnerIterator it(a, k); it; ++it)
142 entries.emplace_back(it.row(), it.col(), it.value());
143
144 for (int k = 0; k < b.outerSize(); ++k)
145 for (StiffnessMatrix::InnerIterator it(b, k); it; ++it)
146 entries.emplace_back(a.rows() + it.row(), a.cols() + it.col(), it.value());
147
148 StiffnessMatrix out(a.rows() + b.rows(), a.cols() + b.cols());
149 out.setFromTriplets(entries.begin(), entries.end());
150 out.makeCompressed();
151 return out;
152 }
153
154 StiffnessMatrix identity_mass(const int size)
155 {
156 return utils::sparse_identity(size, size);
157 }
158
159 } // namespace
160
190
192 const std::string &formulation,
193 const Units &units,
194 const json &args,
195 const std::string &out_path)
196 {
197 VarForm::init(formulation, units, args, out_path);
199
200 const bool is_time_dependent = args.contains("time") && !args["time"].is_null();
201
203 if (args["solver"]["advanced"]["check_inversion"] == "Conservative")
204 {
205 if (auto elastic_assembler = std::dynamic_pointer_cast<assembler::ElasticityAssembler>(primary_assembler_))
206 elastic_assembler->set_use_robust_jacobian();
207 }
208
209 temperature_assembler_ = std::make_shared<assembler::Laplacian>("conductivity");
211 mass_assembler_ = std::make_shared<assembler::Mass>();
212 pure_mass_assembler_ = std::make_shared<assembler::HRZMass>();
213 temperature_mass_assembler_ = std::make_shared<assembler::Mass>(std::make_shared<assembler::ThermalMassDensity>());
214 temperature_pure_mass_assembler_ = std::make_shared<assembler::HRZMass>();
215
216 problem = std::make_shared<assembler::GenericTensorProblem>("ThermoElasticDisplacement");
217 problem->clear();
218 temperature_problem_ = std::make_shared<assembler::GenericScalarProblem>("ThermoElasticTemperature");
219 temperature_problem_->clear();
220
221 json tmp;
222 tmp["is_time_dependent"] = is_time_dependent;
223 problem->set_parameters(tmp, root_path);
224 temperature_problem_->set_parameters(tmp, root_path);
225
226 auto bc = args["boundary_conditions"];
227 bc["root_path"] = root_path;
228 problem->set_parameters(bc, root_path);
229 temperature_problem_->set_parameters(bc, root_path);
230 problem->set_parameters(args["initial_conditions"], root_path);
231 temperature_problem_->set_parameters(args["initial_conditions"], root_path);
232 problem->set_parameters(args["output"], root_path);
233 temperature_problem_->set_parameters(args["output"], root_path);
234
235 problem->set_units(*primary_assembler_, units);
237
238 t0 = is_time_dependent ? args["time"]["t0"].get<double>() : 0.0;
239 time_steps = is_time_dependent ? args["time"]["time_steps"].get<int>() : 0;
240 dt = is_time_dependent ? args["time"]["dt"].get<double>() : 0.0;
241 contact_dhat_was_explicit_ = args["contact"].value("_dhat_was_explicit", false);
242 this->args["contact"].erase("_dhat_was_explicit");
243 }
244
246 {
247 const json material = first_material(args.at("materials"));
248 displacement_space_id_ = material.at("displacement_space_id").get<int>();
249 temperature_space_id_ = material.at("temperature_space_id").get<int>();
251 log_and_throw_error("ThermoElasticity requires distinct displacement and temperature FE spaces.");
252
253 elastic_formulation_ = elastic_formulation_from_thermo_materials(args.at("materials"));
254 assert_same_space_ids(args.at("materials"), displacement_space_id_, temperature_space_id_);
255 }
256
258 {
259 if (args["materials"].is_array())
260 {
261 json materials = json::array();
262 for (const json &material : args["materials"])
263 materials.push_back(elastic_material_from_thermo_material(material));
264 return materials;
265 }
266
267 return elastic_material_from_thermo_material(args["materials"]);
268 }
269
271 {
272 const json &integrators = args["time"]["integrator"];
273 if (!integrators.is_array())
274 return integrators;
275
276 for (const json &integrator : integrators)
277 {
278 if (integrator.value("fe_space", -1) == fe_space_id)
279 {
280 json copy = integrator;
281 copy.erase("fe_space");
282 return copy;
283 }
284 }
285
286 log_and_throw_error("Missing time integrator for FE space {}.", fe_space_id);
287 }
288
289 void ThermoElasticVarForm::load_mesh(const mesh::Mesh &mesh, const json &args)
290 {
291 assert(mesh_);
292 std::vector<int> body_ids(mesh.n_elements());
293 for (int i = 0; i < mesh.n_elements(); ++i)
294 body_ids[i] = mesh.get_body_id(i);
295
296 const json elastic_materials = elastic_material_args();
297
298 primary_assembler_->set_size(mesh.dimension());
299 primary_assembler_->set_materials(body_ids, elastic_materials, units, root_path);
300 thermoelastic_assembler_->set_size(mesh.dimension());
301 thermoelastic_assembler_->set_materials(body_ids, args["materials"], units, root_path);
302 mass_assembler_->set_size(mesh.dimension());
303 mass_assembler_->set_materials(body_ids, elastic_materials, units, root_path);
304 pure_mass_assembler_->set_size(mass_assembler_->size());
305
306 temperature_assembler_->set_size(1);
307 temperature_assembler_->set_materials(body_ids, args["materials"], units, root_path);
308 temperature_mass_assembler_->set_size(1);
309 temperature_mass_assembler_->set_materials(body_ids, args["materials"], units, root_path);
311
312 problem->init(mesh);
313 temperature_problem_->init(mesh);
314
315 logger().info("Loading obstacles...");
317 units,
318 args["geometry"],
319 utils::json_as_array(args["boundary_conditions"]["obstacle_displacements"]),
320 utils::json_as_array(args["boundary_conditions"]["dirichlet_boundary"]),
321 root_path, mesh.dimension());
322 }
323
324 void ThermoElasticVarForm::build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
325 {
326 assert(problem);
327 assert(temperature_problem_);
328 assert(primary_assembler_);
330
331 Eigen::VectorXi displacement_orders;
332 assign_discr_orders(args["space"]["discr_order"], displacement_space_id_, mesh, displacement_orders);
333
334 if (args["space"]["use_p_ref"])
335 {
337 mesh,
338 args["space"]["advanced"]["B"],
339 args["space"]["advanced"]["h1_formula"],
340 args["space"]["discr_order"],
341 args["space"]["advanced"]["discr_order_max"],
342 stats,
343 displacement_orders);
344
345 logger().info("min p: {} max p: {}", displacement_orders.minCoeff(), displacement_orders.maxCoeff());
346 }
347
349 mesh,
350 iso_parametric,
351 displacement_orders,
352 args["space"]["basis_type"],
353 args["space"]["poly_basis_type"],
355 mesh.dimension(),
356 args["space"]["advanced"]["quadrature_order"],
357 args["space"]["advanced"]["mass_quadrature_order"],
358 args["space"]["advanced"]["use_corner_quadrature"],
359 args["space"]["advanced"]["n_harmonic_samples"],
360 args["space"]["advanced"]["integral_constraints"],
361 space_,
362 boundary_);
363
364 problem->update_nodes(space_.space_in_node_to_node);
367
368 const int n_fe_bases = space_.n_bases;
370
371 logger().info("Building collision mesh...");
374 logger().info("Done!");
375
376 for (int i = n_fe_bases; i < space_.n_bases; ++i)
377 {
378 for (int d = 0; d < mesh.dimension(); ++d)
379 boundary_.boundary_nodes.push_back(i * mesh.dimension() + d);
380 }
382
383 build_temperature_basis(mesh, iso_parametric, args);
385
386 const auto &current_bases = space_.geometry_basis_list();
387 if (args["space"]["advanced"]["count_flipped_els"])
388 stats.count_flipped_elements(mesh, current_bases);
389
390 const int n_samples = 10;
391 stats.compute_mesh_size(mesh, current_bases, n_samples, args["output"]["advanced"]["curved_mesh_size"]);
392 logger().info("flipped elements {}", stats.n_flipped);
393 logger().info("h: {}", stats.mesh_size);
394
395 if (std::max(space_.n_bases, temperature_space_.n_bases) <= args["solver"]["advanced"]["cache_size"])
396 {
397 igl::Timer timer;
398 timer.start();
399 logger().info("Building cache...");
400 ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases);
401 mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
402 pure_mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
406 logger().info(" took {}s", timer.getElapsedTime());
407 }
408 else
409 {
416 }
417 }
418
420 {
422
423 problem->setup_bc(
424 mesh,
431 mesh.dimension());
432 std::vector<int> unused_neumann_boundary_nodes;
433 problem->setup_bc(
434 mesh,
440 unused_neumann_boundary_nodes,
441 mesh.dimension());
442
443 problem->setup_nodal_bc(
444 mesh,
449 problem->setup_nodal_bc(
450 mesh,
455
456 for (const int n_id : boundary_.dirichlet_nodes)
457 {
458 const int tag = mesh.get_node_id(n_id);
459 for (int d = 0; d < mesh.dimension(); ++d)
460 if (problem->is_nodal_dimension_dirichlet(n_id, tag, d, displacement_space_id_))
461 boundary_.boundary_nodes.push_back(n_id * mesh.dimension() + d);
462 }
463
467 }
468
469 void ThermoElasticVarForm::build_temperature_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
470 {
471 Eigen::VectorXi temperature_orders;
472 assign_discr_orders(args["space"]["discr_order"], temperature_space_id_, mesh, temperature_orders);
473
475 mesh,
476 iso_parametric,
477 temperature_orders,
478 args["space"]["basis_type"],
479 args["space"]["poly_basis_type"],
481 /*value_dim=*/1,
482 args["space"]["advanced"]["quadrature_order"],
483 args["space"]["advanced"]["mass_quadrature_order"],
484 args["space"]["advanced"]["use_corner_quadrature"],
485 args["space"]["advanced"]["n_harmonic_samples"],
486 args["space"]["advanced"]["integral_constraints"],
490
491 logger().info("n temperature bases: {}", temperature_space_.n_bases);
492 }
493
495 {
497
499
500 temperature_problem_->setup_bc(
501 mesh,
508 /*value_dim=*/1);
509 std::vector<int> unused_neumann_boundary_nodes;
510 temperature_problem_->setup_bc(
511 mesh,
517 unused_neumann_boundary_nodes,
518 /*value_dim=*/1);
519
520 temperature_problem_->setup_nodal_bc(
521 mesh,
526 temperature_problem_->setup_nodal_bc(
527 mesh,
532
533 for (const int n_id : temperature_boundary_.dirichlet_nodes)
535
539 }
540
542 {
543 json rhs_solver_params = args["solver"]["linear"];
544 if (!rhs_solver_params.contains("Pardiso"))
545 rhs_solver_params["Pardiso"] = {};
546 rhs_solver_params["Pardiso"]["mtype"] = -2;
547
548 solve_data.rhs_assembler = std::make_shared<assembler::RhsAssembler>(
554 args["space"]["advanced"]["bc_method"],
555 rhs_solver_params,
558
559 temperature_rhs_assembler_ = std::make_shared<assembler::RhsAssembler>(
560 *temperature_assembler_, *mesh_, nullptr,
563 temperature_space_.n_bases, /*size=*/1,
566 args["space"]["advanced"]["bc_method"],
567 rhs_solver_params,
569 }
570
572 {
573 igl::Timer timer;
574 json p_params = {};
575 p_params["formulation"] = primary_assembler_->name();
576 p_params["root_path"] = root_path;
577 {
578 RowVectorNd min, max, delta;
579 mesh.bounding_box(min, max);
580 delta = (max - min) / 2. + min;
581 if (mesh.is_volume())
582 p_params["bbox_center"] = {delta(0), delta(1), delta(2)};
583 else
584 p_params["bbox_center"] = {delta(0), delta(1)};
585 }
586 problem->set_parameters(p_params, root_path);
587 temperature_problem_->set_parameters(p_params, root_path);
588
589 rhs_.resize(0, 0);
590 temperature_rhs_.resize(0, 0);
591
592 timer.start();
593 logger().info("Assigning rhs...");
594
596 assert(rhs_assembler_ != nullptr);
597 assert(temperature_rhs_assembler_ != nullptr);
598 assert(temperature_rhs_density_ != nullptr);
599 rhs_assembler_->assemble(mass_assembler_->density(), rhs_);
600 rhs_ *= -1;
602 temperature_rhs_ *= -1;
603
604 timings.assigning_rhs_time = timer.getElapsedTime();
605 logger().info(" took {}s", timings.assigning_rhs_time);
606 }
607
609 {
610 mass_.resize(0, 0);
611 pure_mass_.resize(0, 0);
612 temperature_mass_.resize(0, 0);
613 temperature_pure_mass_.resize(0, 0);
614
615 igl::Timer timer;
616 timer.start();
617 logger().info("Assembling mass mat...");
618
623
624 assert(mass_.size() > 0);
625 avg_mass_ = 0;
626 for (int k = 0; k < mass_.outerSize(); ++k)
627 for (StiffnessMatrix::InnerIterator it(mass_, k); it; ++it)
628 avg_mass_ += it.value();
629 avg_mass_ /= mass_.rows();
630 logger().info("average mass {}", avg_mass_);
631
632 if (args["solver"]["advanced"]["lump_mass_matrix"])
633 {
636 }
637
638 stacked_lumped_mass_ = block_diag(
639 pure_mass_.size() > 0 ? pure_mass_ : identity_mass(displacement_ndof()),
640 temperature_pure_mass_.size() > 0 ? temperature_pure_mass_ : identity_mass(temperature_ndof()));
641
642 timer.stop();
643 timings.assembling_mass_mat_time = timer.getElapsedTime();
644 logger().info(" took {}s", timings.assembling_mass_mat_time);
645
648 stats.mat_size = (long long)stacked_lumped_mass_.rows() * (long long)stacked_lumped_mass_.cols();
649 logger().info("sparsity: {}/{}", stats.nn_zero, stats.mat_size);
650 }
651
652 void ThermoElasticVarForm::initial_temperature_solution(Eigen::MatrixXd &solution) const
653 {
654 assert(temperature_rhs_assembler_ != nullptr);
655
656 const bool was_solution_loaded = read_initial_x_from_file(
657 resolve_input_path(args["input"]["data"]["state"]), "temperature",
658 args["input"]["data"]["reorder"], temperature_space_.space_in_node_to_node,
659 /*dim=*/1, solution);
660
661 if (!was_solution_loaded)
662 temperature_rhs_assembler_->initial_solution(solution);
663 }
664
666 const Eigen::MatrixXd &displacement,
667 const Eigen::MatrixXd &temperature) const
668 {
669 assert(displacement.rows() == displacement_ndof());
670 assert(temperature.rows() == temperature_ndof());
671 assert(displacement.cols() == temperature.cols());
672
673 Eigen::MatrixXd solution(displacement.rows() + temperature.rows(), displacement.cols());
674 solution << displacement, temperature;
675 return solution;
676 }
677
679 const Eigen::MatrixXd &solution,
680 Eigen::MatrixXd &displacement,
681 Eigen::MatrixXd &temperature) const
682 {
683 assert(solution.rows() == total_ndof());
684 displacement = solution.topRows(displacement_ndof());
685 temperature = solution.bottomRows(temperature_ndof());
686 }
687
688 void ThermoElasticVarForm::build_forms(Eigen::MatrixXd &solution, const double t)
689 {
690 assert(solution.cols() == 1);
691 assert(solution.rows() == total_ndof());
692
693 const bool is_time_dependent = problem->is_time_dependent();
694 const double form_dt = is_time_dependent ? dt : 0.0;
695
696 stacked_form_ = std::make_shared<solver::StackedForm>();
697 const auto displacement_block = stacked_form_->add_block(displacement_ndof());
698 const auto temperature_block = stacked_form_->add_block(temperature_ndof());
699
700 Eigen::MatrixXd displacement, temperature;
701 split_solution(solution, displacement, temperature);
702
703 if (is_time_dependent)
704 {
708
709 Eigen::MatrixXd displacement_solution, displacement_velocity, displacement_acceleration;
710 initial_elastic_solution(displacement_solution);
711 displacement_solution.col(0) = displacement;
712 initial_velocity(displacement_velocity);
713 initial_acceleration(displacement_acceleration);
714 solve_data.time_integrator->init(displacement_solution, displacement_velocity, displacement_acceleration, dt);
715 }
716 else
717 {
718 solve_data.time_integrator = nullptr;
719 }
720
721 init_forms(args, mesh_->dimension(), displacement, t);
722 for (const auto &form : forms)
723 {
724 assert(form);
725 stacked_form_->add(displacement_block, form);
726 }
727 solve_data.al_form.clear();
728
729 temperature_form_ = std::make_shared<solver::ElasticForm>(
731 *temperature_assembler_, temperature_ass_vals_cache_, t, form_dt, mesh_->is_volume(),
732 /*jacobian_threshold=*/0.0, solver::ElementInversionCheck::Discrete);
733 stacked_form_->add(temperature_block, temperature_form_);
734
736 thermoelastic_form_ = std::make_shared<solver::MixedAssemblerForm>(
740 t, form_dt, mesh_->is_volume());
741 stacked_form_->add(displacement_block, temperature_block, thermoelastic_form_);
742
743 const int gdiscr_order = mesh_->orders().size() <= 0 ? 1 : mesh_->orders().maxCoeff();
744 const QuadratureOrders temperature_boundary_samples =
745 n_boundary_samples(temperature_space_.disc_orders.maxCoeff(), gdiscr_order);
746 temperature_body_form_ = std::make_shared<solver::BodyForm>(
747 temperature_ndof(), 0,
749 temperature_boundary_.local_neumann_boundary, temperature_boundary_samples,
752 /*is_formulation_mixed=*/false, is_time_dependent);
753 temperature_body_form_->update_quantities(t, temperature);
754 stacked_form_->add(temperature_block, temperature_body_form_);
755
756 if (is_time_dependent)
757 {
761
762 Eigen::MatrixXd temperature_solution;
763 initial_temperature_solution(temperature_solution);
764 temperature_solution.col(0) = temperature;
765 Eigen::MatrixXd temperature_velocity = Eigen::MatrixXd::Zero(temperature_solution.rows(), temperature_solution.cols());
766 Eigen::MatrixXd temperature_acceleration = Eigen::MatrixXd::Zero(temperature_solution.rows(), temperature_solution.cols());
767 temperature_time_integrator_->init(temperature_solution, temperature_velocity, temperature_acceleration, dt);
768
769 temperature_inertia_form_ = std::make_shared<solver::InertiaForm>(temperature_mass_, *temperature_time_integrator_);
771 {
772 temperature_inertia_form_->set_x_tilde_updater(
773 [this, temperature_boundary_samples](
774 const double t,
775 const Eigen::VectorXd &,
776 Eigen::VectorXd &target) {
777 Eigen::MatrixXd projected_target = target;
778 const std::vector<mesh::LocalBoundary> empty_neumann_boundary;
782 temperature_boundary_samples,
783 empty_neumann_boundary,
784 projected_target,
785 Eigen::MatrixXd(),
786 t);
787 assert(projected_target.cols() == 1);
788 target = projected_target.col(0);
789 });
790 }
791 stacked_form_->add(temperature_block, temperature_inertia_form_);
792
794 }
795 else
796 {
799 }
800
801 forms.clear();
802 forms.push_back(stacked_form_);
803 for (const auto &form : forms)
804 form->set_output_dir(output_path);
805
806 solve_data.al_form.clear();
808 {
809 auto stacked_al = std::make_shared<solver::StackedAugmentedLagrangianForm>();
810 const auto displacement_al_block = stacked_al->add_block(displacement_block.size());
811 const auto temperature_al_block = stacked_al->add_block(temperature_block.size());
812
813 if (!boundary_.boundary_nodes.empty())
814 {
815 stacked_al->add(
816 displacement_al_block,
817 std::make_shared<solver::BCLagrangianForm>(
818 displacement_block.size(),
821 obstacle.n_vertices() * mesh_->dimension(), is_time_dependent, t));
822 }
823
825 {
826 stacked_al->add(
827 temperature_al_block,
828 std::make_shared<solver::BCLagrangianForm>(
829 temperature_block.size(),
831 temperature_boundary_.local_neumann_boundary, temperature_boundary_samples,
833 /*obstacle_ndof=*/0, is_time_dependent, t));
834 }
835
836 solve_data.al_form.push_back(stacked_al);
837 }
838 }
839
841 {
842 assert(problem->is_time_dependent());
845
847
848 const double displacement_scaling = solve_data.time_integrator->acceleration_scaling();
849 const double temperature_scaling = temperature_time_integrator_->acceleration_scaling();
850
852 temperature_form_->set_weight(temperature_scaling);
854 temperature_body_form_->set_weight(temperature_scaling);
856 thermoelastic_form_->set_row_weights(displacement_scaling, temperature_scaling);
857 }
858
859 void ThermoElasticVarForm::solve_nonlinear_step(const int step, Eigen::MatrixXd &solution)
860 {
861 assert(solve_data.nl_problem != nullptr);
863
864 const json nonlinear_params = solver_params_for_residual_mode(args["solver"]["nonlinear"], nl_problem.is_residual());
865 const json al_nonlinear_params = solver_params_for_residual_mode(args["solver"]["augmented_lagrangian"]["nonlinear"], nl_problem.is_residual());
866
867 std::shared_ptr<polysolve::nonlinear::Solver> nl_solver =
868 polysolve::nonlinear::Solver::create(
869 nonlinear_params, args["solver"]["linear"],
871
872 if (nl_problem.uses_lagging())
873 nl_problem.init_lagging(solution);
874
875 const auto update_displacement_barrier_stiffness = [&](const Eigen::VectorXd &x) {
876 const Eigen::VectorXd displacement = x.head(displacement_ndof());
878 };
879
880 if (!solve_data.al_form.empty())
881 {
882 solver::ALSolver al_solver(
884 args["solver"]["augmented_lagrangian"]["initial_weight"],
885 args["solver"]["augmented_lagrangian"]["scaling"],
886 args["solver"]["augmented_lagrangian"]["max_weight"],
887 args["solver"]["augmented_lagrangian"]["eta"],
888 update_displacement_barrier_stiffness);
889
890 al_solver.post_subsolve = [&](const double al_weight) {
891 stats.solver_info.push_back(
892 {{"type", al_weight > 0 ? "al" : "rc"},
893 {"t", step},
894 {"info", nl_solver->info()}});
895 if (al_weight > 0)
896 stats.solver_info.back()["weight"] = al_weight;
897 save_subsolve(stats.solver_info.size(), step, solution);
898 };
899
900 al_solver.solve_al(
901 nl_problem, solution, al_nonlinear_params,
902 args["solver"]["linear"], units.characteristic_length(), nl_solver);
903 al_solver.solve_reduced(
904 nl_problem, solution, nonlinear_params,
905 args["solver"]["linear"], units.characteristic_length(), nl_solver);
906 return;
907 }
908
909 Eigen::VectorXd x = solution;
910 nl_problem.init(x);
911 update_displacement_barrier_stiffness(x);
912 nl_problem.normalize_forms();
913 nl_solver->minimize(nl_problem, x);
914 nl_problem.finish();
915 solution = x;
916 stats.solver_info.push_back({{"type", "rc"}, {"t", step}, {"info", nl_solver->info()}});
917 save_subsolve(stats.solver_info.size(), step, solution);
918 }
919
920 void ThermoElasticVarForm::solve_problem(Eigen::MatrixXd &sol)
921 {
922 stats.spectrum.setZero();
923
924 igl::Timer timer;
925 timer.start();
926 logger().info("Solving ThermoElasticity");
927
928 {
929 POLYFEM_SCOPED_TIMER("Setup RHS");
930
931 if (sol.size() <= 0)
932 {
933 Eigen::MatrixXd displacement, temperature;
934 initial_elastic_solution(displacement);
935 initial_temperature_solution(temperature);
936 const int cols = std::max(displacement.cols(), temperature.cols());
937 if (displacement.cols() != cols)
938 displacement.conservativeResize(Eigen::NoChange, cols);
939 if (temperature.cols() != cols)
940 temperature.conservativeResize(Eigen::NoChange, cols);
941 sol = stacked_solution(displacement, temperature);
942 }
943
944 if (sol.cols() > 1)
945 sol.conservativeResize(Eigen::NoChange, 1);
946 }
947
948 build_forms(sol, problem->is_time_dependent() ? t0 + dt : 1.0);
949
950 double characteristic_length = 0;
951 if (args["solver"]["advanced"]["characteristic_length"] > 0)
952 characteristic_length = args["solver"]["advanced"]["characteristic_length"];
953 else
954 {
955 RowVectorNd min, max;
956 mesh_->bounding_box(min, max);
957 characteristic_length = (max - min).norm();
958 }
959
960 double characteristic_force_density = 0;
961 if (args["solver"]["advanced"]["characteristic_force_density"] <= 0)
962 {
963 logger().warn("No user-specified force density was provided, defaulting to 10000.");
964 characteristic_force_density = 10000;
965 }
966 else
967 characteristic_force_density = args["solver"]["advanced"]["characteristic_force_density"];
968
969 solve_data.nl_problem = std::make_shared<solver::NLProblem>(
970 total_ndof(), nullptr, problem->is_time_dependent() ? t0 + dt : 1.0,
972 polysolve::linear::Solver::create(args["solver"]["linear"], logger()),
973 characteristic_length, characteristic_force_density,
974 stacked_lumped_mass_.size() > 0 ? stacked_lumped_mass_ : identity_mass(total_ndof()),
975 mesh_->dimension(),
976 problem->is_time_dependent());
977 solve_data.nl_problem->init(sol);
978 solve_data.nl_problem->update_quantities(problem->is_time_dependent() ? t0 + dt : 1.0, sol);
979 stats.solver_info = json::array();
980
981 if (!problem->is_time_dependent())
982 {
983 solve_nonlinear_step(0, sol);
984 }
985 else
986 {
987 save_timestep(t0, 0, t0, dt, sol);
988 for (int t = 1; t <= time_steps; ++t)
989 {
990 const double time = t0 + dt * t;
991 solve_nonlinear_step(t, sol);
992
993 save_timestep(time, t, t0, dt, sol);
994
995 Eigen::MatrixXd displacement, temperature;
996 split_solution(sol, displacement, temperature);
997 solve_data.time_integrator->update_quantities(displacement);
998 temperature_time_integrator_->update_quantities(temperature);
1001 solve_data.nl_problem->update_quantities(t0 + (t + 1) * dt, sol);
1002
1003 logger().info("{}/{} t={}", t, time_steps, time);
1005 save_step_state(t0, dt, t, nullptr);
1006 }
1007 }
1008
1009 timer.stop();
1010 timings.solving_time = timer.getElapsedTime();
1011 logger().info(" took {}s", timings.solving_time);
1012 }
1013
1015 {
1016 if (!args["output"]["advanced"]["compute_error"])
1017 return stats;
1018
1019 Eigen::MatrixXd displacement, temperature;
1020 split_solution(solution, displacement, temperature);
1021
1022 double tend = 0;
1023 if (!args["time"].is_null())
1024 tend = args["time"]["tend"];
1025
1027 return stats;
1028 }
1029
1030 std::vector<io::OutputField> ThermoElasticVarForm::output_fields(
1031 const io::OutputSample &sample,
1032 const Eigen::MatrixXd &solution,
1033 const io::OutputFieldOptions &options) const
1034 {
1035 Eigen::MatrixXd displacement, temperature;
1036 split_solution(solution, displacement, temperature);
1037
1038 std::vector<io::OutputField> fields =
1039 NonlinearElasticVarForm::output_fields(sample, displacement, options);
1040 fields.erase(
1041 std::remove_if(
1042 fields.begin(), fields.end(),
1043 [](const io::OutputField &field) {
1044 return field.name == "solution" || field.name == "solution_gradient";
1045 }),
1046 fields.end());
1047
1048 if (!mesh_ || temperature.size() <= 0)
1049 return fields;
1051 return fields;
1052
1053 const bool has_element_samples = sample.local_points.rows() > 0 && sample.local_points.rows() == sample.element_ids.size();
1054 const int output_rows = sample.points.rows() > 0 ? sample.points.rows() : std::max<int>(sample.local_points.rows(), sample.node_ids.size());
1055
1056 const auto has_field = [&](const std::string &name) {
1057 return std::any_of(fields.begin(), fields.end(), [&](const io::OutputField &field) {
1058 return field.name == name;
1059 });
1060 };
1061
1062 const auto append_material_fields = [&](const assembler::Assembler &assembler) {
1063 const auto &paraview_options = args["output"]["paraview"]["options"];
1064 if (!paraview_options["material"] || !has_element_samples)
1065 return;
1066
1067 const auto params = assembler.parameters();
1068 std::map<std::string, Eigen::MatrixXd> param_values;
1069 for (const auto &[p, _] : params)
1070 param_values[p].setZero(output_rows, 1);
1071
1072 for (int i = 0; i < sample.local_points.rows(); ++i)
1073 {
1074 const int element_id = sample.element_ids(i);
1075 if (element_id < 0)
1076 continue;
1077
1078 for (const auto &[p, func] : params)
1079 param_values.at(p)(i) = func(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
1080 }
1081
1082 for (const auto &[name, values] : param_values)
1083 if (options.export_field(name) && !has_field(name))
1084 fields.push_back({name, values, io::OutputField::Association::Point});
1085 };
1086
1087 const auto sample_temperature = [&](Eigen::MatrixXd &values, Eigen::MatrixXd *gradients = nullptr) -> bool {
1088 if (has_element_samples)
1089 {
1090 values.resize(sample.local_points.rows(), 1);
1091 if (gradients)
1092 gradients->resize(sample.local_points.rows(), mesh_->dimension());
1093 for (int i = 0; i < sample.local_points.rows(); ++i)
1094 {
1095 const int element_id = sample.element_ids(i);
1096 if (element_id < 0)
1097 {
1098 values(i) = 0;
1099 if (gradients)
1100 gradients->row(i).setZero();
1101 continue;
1102 }
1103
1104 Eigen::MatrixXd local_sol, local_grad;
1106 *mesh_, 1, temperature_space_.basis_list(), temperature_space_.geometry_basis_list(),
1107 element_id, sample.local_points.row(i), temperature, local_sol, local_grad);
1108 values(i) = local_sol(0);
1109 if (gradients)
1110 gradients->row(i) = local_grad;
1111 }
1112
1113 if (output_rows > values.rows())
1114 {
1115 const int previous_rows = values.rows();
1116 values.conservativeResize(output_rows, Eigen::NoChange);
1117 values.bottomRows(output_rows - previous_rows).setZero();
1118 if (gradients)
1119 {
1120 gradients->conservativeResize(output_rows, Eigen::NoChange);
1121 gradients->bottomRows(output_rows - previous_rows).setZero();
1122 }
1123 }
1124 return true;
1125 }
1126
1127 if (sample.node_ids.size() > 0)
1128 {
1129 values.resize(sample.node_ids.size(), 1);
1130 for (int i = 0; i < sample.node_ids.size(); ++i)
1131 {
1132 const int node_id = sample.node_ids(i);
1133 if (node_id < 0 || node_id >= temperature.rows())
1134 return false;
1135 values(i) = temperature(node_id);
1136 }
1137 return sample.points.rows() == 0 || sample.points.rows() == values.rows();
1138 }
1139
1140 return false;
1141 };
1142
1143 const bool export_temperature_gradient =
1144 !options.fields.empty() && options.export_field("temperature_gradient");
1145 if (options.export_field("temperature") || export_temperature_gradient)
1146 {
1147 Eigen::MatrixXd values, gradients;
1148 if (sample_temperature(values, export_temperature_gradient ? &gradients : nullptr))
1149 {
1150 if (options.export_field("temperature"))
1151 fields.push_back({"temperature", values, io::OutputField::Association::Point});
1152 if (export_temperature_gradient)
1153 fields.push_back({"temperature_gradient", gradients, io::OutputField::Association::Point});
1154 }
1155 }
1156
1157 if (thermoelastic_assembler_)
1158 append_material_fields(*thermoelastic_assembler_);
1159 if (temperature_assembler_)
1160 append_material_fields(*temperature_assembler_);
1161 if (temperature_mass_assembler_)
1162 append_material_fields(*temperature_mass_assembler_);
1163
1164 return fields;
1165 }
1166} // namespace polyfem::varform
std::vector< Eigen::Triplet< double > > entries
int x
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
double characteristic_length() const
Definition Units.hpp:22
static std::shared_ptr< MixedNLAssembler > make_mixed_nl_assembler(const std::string &formulation)
static bool is_elastic_material(const std::string &material)
utility to check if material is one of the elastic materials
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.
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)
double assigning_rhs_time
time to computing the rhs
double assembling_mass_mat_time
time to assembly mass
double solving_time
time to solve
all stats from polyfem
int n_flipped
number of flipped elements, compute only when using count_flipped_els (false by default)
json solver_info
information of the solver, eg num iteration, time, errors, etc the informations varies depending on t...
Eigen::Vector4d spectrum
spectrum of the stiffness matrix, enable only if POLYSOLVE_WITH_SPECTRA is ON (off 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:1811
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:1856
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:1728
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
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:41
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
Definition Mesh.hpp:163
virtual int get_body_id(const int primitive) const
Get the volume selection of an element (cell in 3d, face in 2d)
Definition Mesh.hpp:514
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:401
int dimension() const
utily for dimension
Definition Mesh.hpp:153
virtual int get_node_id(const int node_id) const
Get the boundary selection of a node.
Definition Mesh.hpp:497
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
void solve_reduced(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
Definition ALSolver.hpp:41
std::function< void(const double)> post_subsolve
Definition ALSolver.hpp:53
void solve_al(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
Definition ALSolver.hpp:29
bool is_residual() const override
virtual void init(const TVector &x0) override
double normalize_forms() override
void init_lagging(const TVector &x) override
void update_dt()
updates the dt inside the different forms
std::shared_ptr< solver::NLProblem > nl_problem
std::vector< std::shared_ptr< solver::AugmentedLagrangianForm > > al_form
std::shared_ptr< time_integrator::ImplicitTimeIntegrator > time_integrator
void update_barrier_stiffness(const Eigen::VectorXd &x)
update the barrier stiffness for the forms
std::shared_ptr< assembler::RhsAssembler > rhs_assembler
static std::shared_ptr< ImplicitTimeIntegrator > construct_time_integrator(const json &params, DynamicOrder dynamic_order=DynamicOrder::Second)
Factory method for constructing an implicit time integrator.
assembler::AssemblyValsCache pure_mass_ass_vals_cache_
std::shared_ptr< assembler::Assembler > primary_assembler_
QuadratureOrders elastic_boundary_samples() const
assembler::AssemblyValsCache ass_vals_cache_
void initial_velocity(Eigen::MatrixXd &velocity) const
std::shared_ptr< assembler::HRZMass > pure_mass_assembler_
void initial_elastic_solution(Eigen::MatrixXd &solution) const
std::shared_ptr< assembler::Mass > mass_assembler_
void initial_acceleration(Eigen::MatrixXd &acceleration) const
std::shared_ptr< assembler::RhsAssembler > rhs_assembler_
assembler::AssemblyValsCache mass_ass_vals_cache_
const std::vector< basis::ElementBases > & geometry_basis_list() const
Definition FESpace.hpp:115
std::shared_ptr< std::vector< basis::ElementBases > > bases
Per-element basis data.
Definition FESpace.hpp:68
std::shared_ptr< GeometryMapping > geometry
Geometric mapping used to integrate this FE space.
Definition FESpace.hpp:89
Eigen::VectorXi disc_orders
Primary polynomial degree for each mesh element.
Definition FESpace.hpp:71
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
const std::vector< basis::ElementBases > & basis_list() const
Definition FESpace.hpp:109
void init_forms(const json &args, const int dim, Eigen::MatrixXd &sol, const double t)
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 build_collision_mesh(const mesh::Mesh &mesh, const json &args)
std::vector< std::shared_ptr< solver::Form > > forms
void build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args) 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.
std::shared_ptr< solver::BodyForm > temperature_body_form_
std::shared_ptr< assembler::HRZMass > temperature_pure_mass_assembler_
std::shared_ptr< assembler::MixedNLAssembler > thermoelastic_assembler_
assembler::AssemblyValsCache temperature_mass_ass_vals_cache_
std::shared_ptr< time_integrator::ImplicitTimeIntegrator > temperature_time_integrator_
std::shared_ptr< solver::MixedAssemblerForm > thermoelastic_form_
json time_integrator_args(const int fe_space_id) const
void build_forms(Eigen::MatrixXd &solution, const double t)
void load_mesh(const mesh::Mesh &mesh, const json &args) override
std::shared_ptr< solver::ElasticForm > temperature_form_
void solve_nonlinear_step(const int step, Eigen::MatrixXd &solution)
io::OutStatsData compute_errors(const Eigen::MatrixXd &solution) override
Get the error statistics of the variational formulation, for output purposes.
void assemble_rhs(const mesh::Mesh &mesh) override
assembler::AssemblyValsCache temperature_ass_vals_cache_
assembler::AssemblyValsCache temperature_pure_mass_ass_vals_cache_
void assemble_mass_mat(const mesh::Mesh &mesh, const json &args) override
std::shared_ptr< assembler::Problem > temperature_problem_
std::string name() const override
Get the name of the variational formulation.
void split_solution(const Eigen::MatrixXd &solution, Eigen::MatrixXd &displacement, Eigen::MatrixXd &temperature) const
std::shared_ptr< assembler::Assembler > temperature_assembler_
Eigen::MatrixXd stacked_solution(const Eigen::MatrixXd &displacement, const Eigen::MatrixXd &temperature) const
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 build_temperature_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
void initial_temperature_solution(Eigen::MatrixXd &solution) const
std::shared_ptr< assembler::Density > temperature_rhs_density_
std::shared_ptr< solver::InertiaForm > temperature_inertia_form_
std::shared_ptr< assembler::RhsAssembler > temperature_rhs_assembler_
void solve_problem(Eigen::MatrixXd &sol) override
std::shared_ptr< solver::StackedForm > stacked_form_
std::shared_ptr< assembler::Mass > temperature_mass_assembler_
std::string resolve_input_path(const std::string &path, const bool only_if_exists=false) const
Definition VarForm.cpp:1052
static void rebuild_node_positions(const std::vector< basis::ElementBases > &bases, const std::vector< int > &node_ids, std::vector< RowVectorNd > &positions)
Definition VarForm.cpp:1066
std::shared_ptr< assembler::Problem > problem
current problem, it contains rhs and bc
Definition VarForm.hpp:190
std::unique_ptr< mesh::Mesh > mesh_
Definition VarForm.hpp:202
io::OutStatsData stats
Definition VarForm.hpp:194
void notify_time_step(const int t, const int time_steps, const double t0, const double dt) const
Definition VarForm.cpp:946
static bool read_initial_x_from_file(const std::string &state_path, const std::string &x_name, const bool reorder, const Eigen::VectorXi &in_node_to_node, const int dim, Eigen::MatrixXd &x)
Definition VarForm.cpp:38
void save_subsolve(const int i, const int t, const Eigen::MatrixXd &solution) const
Definition VarForm.cpp:927
void save_timestep(const double time, const int t, const double t0, const double dt, const Eigen::MatrixXd &solution) const
Definition VarForm.cpp:902
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:279
void build_fe_space(mesh::Mesh &mesh, const bool iso_parametric, const Eigen::VectorXi &disc_orders, 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:322
io::OutRuntimeData timings
runtime statistics
Definition VarForm.hpp:197
void save_step_state(const double t0, const double dt, const int t, const time_integrator::ImplicitTimeIntegrator *time_integrator, const bool rest_mesh_written=false) const
Definition VarForm.cpp:887
QuadratureOrders n_boundary_samples(const int discr_order, const int gdiscr_order) const
Definition VarForm.cpp:258
void assign_discr_orders(const json &discr_order, const mesh::Mesh &mesh, Eigen::VectorXi &disc_orders)
Definition VarForm.cpp:744
Obstacle read_obstacle_geometry(const Units &units, const json &geometry, const std::vector< json > &displacements, const std::vector< json > &dirichlets, const std::string &root_path, const int dim, const std::vector< std::string > &_names, const std::vector< Eigen::MatrixXd > &_vertices, const std::vector< Eigen::MatrixXi > &_cells, const bool non_conforming)
read a FEM mesh from a geometry JSON
Eigen::SparseMatrix< double > lump_matrix(const Eigen::SparseMatrix< double > &M)
Lump each row of a matrix into the diagonal.
Eigen::SparseMatrix< double > sparse_identity(int rows, int cols)
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:38
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:51
Eigen::VectorXi node_ids
Eigen::VectorXi element_ids
Eigen::MatrixXd local_points
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