PolyFEM
Loading...
Searching...
No Matches
BilaplacianVarForm.cpp
Go to the documentation of this file.
2
19
20#include <polysolve/linear/FEMSolver.hpp>
21
22namespace polyfem::varform
23{
24 using namespace varform::internal;
25
28 const Eigen::MatrixXd &solution,
29 const io::CheckpointMetadata &metadata) const
30 {
31 VarForm::serialize_checkpoint(writer, solution, metadata);
32 write_checkpoint_ordering(writer, "primary", space_);
33 write_checkpoint_ordering(writer, "pressure", pressure_space_);
34 }
35
37 const io::CheckpointReader &reader,
38 Eigen::MatrixXd &solution)
39 {
40 VarForm::deserialize_checkpoint(reader, solution);
42 reorder_checkpoint_block(reader, "primary", space_, 1, 0, solution);
44 reader, "pressure", pressure_space_,
45 1, space_.n_bases, solution);
46 }
47
49 {
51 space_.reset();
58 rhs_assembler_ = nullptr;
59 mass_.resize(0, 0);
60 pure_mass_.resize(0, 0);
61 avg_mass_ = 0;
62 rhs_.resize(0, 0);
66 primary_assembler_ = nullptr;
67 mass_assembler_ = nullptr;
68 pure_mass_assembler_ = nullptr;
69 mixed_assembler_ = nullptr;
70 pressure_assembler_ = nullptr;
71 t0 = 0;
72 time_steps = 0;
73 dt = 0;
74 time_integrator = nullptr;
75 }
76
77 void BilaplacianVarForm::init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path)
78 {
79 for (const std::string field : {"pressure_boundary", "pressure_cavity"})
80 {
81 const json::json_pointer path("/boundary_conditions/" + field);
82 if (args.contains(path) && !args.at(path).empty())
83 log_and_throw_error("{} does not support boundary_conditions.{}.", formulation, field);
84 }
85
86 VarForm::init(formulation, units, args, out_path);
87 const bool is_time_dependent = args.contains("time") && !args["time"].is_null();
88 const json &discr_orders = args.at("space").at("discr_order");
89
90 const json &materials = args.at("materials");
91 if (materials.is_array() && materials.empty())
92 log_and_throw_error("Bilaplacian formulations require at least one material.");
93 const json &first_material = materials.is_array() ? materials.at(0) : materials;
94 solution_space_id_ = first_material.at("solution_space_id").get<int>();
95 auxiliary_space_id_ = first_material.at("auxiliary_space_id").get<int>();
97 log_and_throw_error("Bilaplacian solution and auxiliary fields must use different FE space IDs.");
98
99 if (discr_orders.is_array())
100 {
101 bool has_solution_space = false;
102 bool has_auxiliary_space = false;
103 for (const json &entry : discr_orders)
104 {
105 const int fe_space_id = entry.at("fe_space").get<int>();
106 has_solution_space |= fe_space_id == solution_space_id_;
107 has_auxiliary_space |= fe_space_id == auxiliary_space_id_;
108 }
109 if (!has_solution_space || !has_auxiliary_space)
110 log_and_throw_error("Bilaplacian discretization-order lists must explicitly name the solution and auxiliary FE spaces.");
111 }
112
113 if (materials.is_array())
114 {
115 for (const json &material : materials)
116 {
117 if (material.at("solution_space_id").get<int>() != solution_space_id_
118 || material.at("auxiliary_space_id").get<int>() != auxiliary_space_id_)
119 log_and_throw_error("All Bilaplacian materials must use the same solution and auxiliary FE space IDs.");
120 }
121 }
122
124 mass_assembler_ = std::make_shared<assembler::Mass>();
125 pure_mass_assembler_ = std::make_shared<assembler::HRZMass>();
128
129 if (!args.contains("preset_problem"))
130 {
131 problem = std::make_shared<assembler::GenericScalarProblem>("GenericScalar");
132 problem->clear();
133 json tmp;
134 tmp["is_time_dependent"] = is_time_dependent;
135 problem->set_parameters(tmp, resources_);
136
137 auto bc = args["boundary_conditions"];
138 problem->set_parameters(bc, resources_);
139 problem->set_parameters(args["initial_conditions"], resources_);
140 problem->set_parameters(args["output"], resources_);
141 }
142 else
143 {
144 problem = problem::ProblemFactory::factory().get_problem(args["preset_problem"]["type"]);
145 problem->clear();
146 problem->set_parameters(args["preset_problem"], resources_);
147 }
148
149 problem->set_units(*primary_assembler_, units);
150 t0 = is_time_dependent ? args["time"]["t0"].get<double>() : 0.0;
151 time_steps = is_time_dependent ? args["time"]["time_steps"].get<int>() : 0;
152 dt = is_time_dependent ? args["time"]["dt"].get<double>() : 0.0;
153 }
154
155 void BilaplacianVarForm::save_json(const Eigen::MatrixXd &solution, std::ostream &out) const
156 {
157 if (!mesh_)
158 {
159 logger().error("Load the mesh first!");
160 return;
161 }
162 if (solution.size() <= 0)
163 {
164 logger().error("Solve the problem first!");
165 return;
166 }
167
168 logger().info("Saving json...");
169 const Eigen::MatrixXd stats_solution =
170 solution.rows() >= space_.n_bases
171 ? solution.topRows(space_.n_bases).eval()
172 : solution;
173
174 nlohmann::json j;
177 stats_solution, *mesh_, space_.disc_orders, space_.disc_ordersq, *problem,
179 args["output"]["advanced"]["sol_at_node"], j);
180 out << j.dump(4) << std::endl;
181 }
182
184 {
185 Eigen::VectorXi output_orders = space_.disc_orders;
186 if (mesh_ && space_.disc_ordersq.size() == space_.disc_orders.size())
187 {
188 for (int e = 0; e < output_orders.size(); ++e)
189 {
190 if (mesh_->is_prism(e))
191 output_orders(e) = std::max(space_.disc_orders(e), space_.disc_ordersq(e));
192 }
193 }
194
195 return {
196 mesh_.get(),
198 output_orders,
199 &space_.polys,
202 nullptr,
203 nullptr,
206 }
207
209 {
210 if (!args["output"]["advanced"]["compute_error"])
211 return stats;
212
213 double tend = 0;
214 if (!args["time"].is_null())
215 tend = args["time"]["tend"];
216
217 Eigen::MatrixXd value, pressure;
218 split_solution(solution, value, pressure);
220 return stats;
221 }
222
223 void BilaplacianVarForm::export_data(const Eigen::MatrixXd &solution) const
224 {
225 const io::OutputSpace space = output_space();
226 if (!space.mesh)
227 {
228 logger().error("Load the mesh first!");
229 return;
230 }
231 if (solution.size() <= 0)
232 {
233 logger().error("Solve the problem first!");
234 return;
235 }
236
238
239 const std::string vis_mesh_path = resolve_output_path(args["output"]["paraview"]["file_name"]);
240 const bool has_time = args.contains("time") && !args["time"].is_null();
241 double tend = has_time ? args["time"]["tend"].get<double>() : 1.0;
242 double dt = 1;
243 if (has_time)
244 dt = args["time"]["dt"];
245
246 const auto opts = export_options(space);
248 space,
249 output_field_function(solution, opts),
250 has_time,
251 tend, dt,
252 opts,
253 vis_mesh_path);
254
255 Eigen::MatrixXd value, pressure;
256 split_solution(solution, value, pressure);
257
258 const std::string solution_path = resolve_output_path(args["output"]["data"]["solution"]);
259 if (!solution_path.empty())
260 {
261 const int primary_ndof = std::min<int>(value.rows(), space_.n_bases);
262 const Eigen::MatrixXd primary_solution = value.topRows(primary_ndof);
263 if (opts.reorder_output && space_.space_in_node_to_node.size() > 0)
264 {
265 const Eigen::MatrixXd nodal_solution = utils::unflatten(primary_solution, 1);
266 Eigen::MatrixXd reordered = Eigen::MatrixXd::Zero(nodal_solution.rows(), nodal_solution.cols());
267 for (int input_node = 0; input_node < space_.space_in_node_to_node.size(); ++input_node)
268 {
269 const int node = space_.space_in_node_to_node(input_node);
270 if (node >= 0 && node < nodal_solution.rows() && input_node < reordered.rows())
271 reordered.row(input_node) = nodal_solution.row(node);
272 }
273 io::write_matrix(solution_path, reordered);
274 }
275 else
276 {
277 io::write_matrix(solution_path, primary_solution);
278 }
279 }
280
281 const std::string nodes_path = resolve_output_path(args["output"]["data"]["nodes"]);
282 if (!nodes_path.empty())
283 {
284 Eigen::MatrixXd nodes = Eigen::MatrixXd::Zero(space_.n_bases, mesh_->dimension());
285 for (const basis::ElementBases &element_bases : space_.basis_list())
286 for (const basis::Basis &basis : element_bases.bases)
287 for (const auto &global : basis.global())
288 nodes.row(global.index) = global.node;
289 io::write_matrix(nodes_path, nodes);
290 }
291
292 const std::string stress_path = resolve_output_path(args["output"]["data"]["stress_mat"]);
293 const std::string mises_path = resolve_output_path(args["output"]["data"]["mises"]);
294 if ((!stress_path.empty() || !mises_path.empty()) && primary_assembler_)
295 {
296 Eigen::MatrixXd stress;
297 Eigen::VectorXd mises;
301 stress, mises);
302 if (!stress_path.empty())
303 io::write_matrix(stress_path, stress);
304 if (!mises_path.empty())
305 io::write_matrix(mises_path, mises);
306 }
307 }
308
309 void BilaplacianVarForm::load_mesh(const mesh::Mesh &mesh, const json &args)
310 {
313 pure_mass_assembler_->set_size(mass_assembler_->size());
314 problem->init(mesh);
315
317 mixed_assembler_->set_size(1);
320 }
321
322 std::shared_ptr<assembler::RhsAssembler> BilaplacianVarForm::build_rhs_assembler(
323 const int n_bases,
324 const std::vector<basis::ElementBases> &bases,
325 const assembler::AssemblyValsCache &ass_vals_cache,
326 const int fe_space_id)
327 {
328 json rhs_solver_params = args["solver"]["linear"];
329 if (!rhs_solver_params.contains("Pardiso"))
330 rhs_solver_params["Pardiso"] = {};
331 rhs_solver_params["Pardiso"]["mtype"] = -2;
332
333 return std::make_shared<assembler::RhsAssembler>(
334 *primary_assembler_, *mesh_, nullptr,
337 n_bases, 1, bases, space_.geometry_basis_list(), ass_vals_cache, *problem, resources_,
338 args["space"]["advanced"]["bc_method"],
339 rhs_solver_params,
340 fe_space_id);
341 }
342
343 void BilaplacianVarForm::build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
344 {
345 assert(problem);
346 assert(primary_assembler_);
347 assert(mass_assembler_);
348 assert(pure_mass_assembler_);
349
350 Eigen::VectorXi space_disc_orders, space_disc_ordersq;
351 assign_discr_orders(args["space"], solution_space_id_, mesh, space_disc_orders, space_disc_ordersq);
352
353 if (args["space"]["use_p_ref"])
354 {
356 mesh,
357 args["space"]["advanced"]["B"],
358 args["space"]["advanced"]["h1_formula"],
359 args["space"]["discr_order"],
360 args["space"]["advanced"]["discr_order_max"],
361 stats,
362 space_disc_orders);
363
364 logger().info("min p: {} max p: {}", space_disc_orders.minCoeff(), space_disc_orders.maxCoeff());
365 }
366
368 mesh,
369 iso_parametric,
370 space_disc_orders,
371 space_disc_ordersq,
372 args["space"]["basis_type"],
373 args["space"]["poly_basis_type"],
375 /*value_dim=*/1,
376 args["space"]["advanced"]["quadrature_order"],
377 args["space"]["advanced"]["mass_quadrature_order"],
378 args["space"]["advanced"]["use_corner_quadrature"],
379 args["space"]["advanced"]["n_harmonic_samples"],
380 args["space"]["advanced"]["integral_constraints"],
381 space_,
382 boundary_);
383
384 problem->update_nodes(space_.space_in_node_to_node);
386
387 const auto &current_bases = space_.geometry_basis_list();
388 if (args["space"]["advanced"]["count_flipped_els"])
389 stats.count_flipped_elements(mesh, current_bases);
390
391 const int n_samples = 10;
392 stats.compute_mesh_size(mesh, current_bases, n_samples, args["output"]["advanced"]["curved_mesh_size"]);
393
394 logger().info("flipped elements {}", stats.n_flipped);
395 logger().info("h: {}", stats.mesh_size);
396
397 if (space_.n_bases <= args["solver"]["advanced"]["cache_size"])
398 {
399 igl::Timer timer;
400 timer.start();
401 logger().info("Building cache...");
402 ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases);
403 mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
404 pure_mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
405 logger().info(" took {}s", timer.getElapsedTime());
406 }
407 else
408 {
412 }
413
414 if (space_.disc_orders.maxCoeff() != space_.disc_orders.minCoeff())
415 log_and_throw_error("p refinement not supported in mixed formulation!");
416 if (!space_.poly_edge_to_data.empty())
417 log_and_throw_error("Polygonal bases are not supported in mixed formulations!");
418
419 const int prev_bases = space_.n_bases;
420 const bool use_corner_quadrature = args["space"]["advanced"]["use_corner_quadrature"];
421 const int quadrature_order = args["space"]["advanced"]["quadrature_order"].get<int>();
422 const int mass_quadrature_order = args["space"]["advanced"]["mass_quadrature_order"].get<int>();
423 Eigen::VectorXi pressure_disc_orders, pressure_disc_ordersq;
424 assign_discr_orders(args["space"], auxiliary_space_id_, mesh, pressure_disc_orders, pressure_disc_ordersq);
425 // to avoid serendipity
426 const std::string pressure_basis_type = args["space"]["basis_type"].get<std::string>() == "Bernstein" ? "Bernstein" : "Lagrange";
428 mesh,
429 /*iso_parametric=*/true,
430 pressure_disc_orders,
431 pressure_disc_ordersq,
432 pressure_basis_type,
433 args["space"]["poly_basis_type"],
435 /*value_dim=*/1,
436 quadrature_order,
437 mass_quadrature_order,
438 use_corner_quadrature,
439 args["space"]["advanced"]["n_harmonic_samples"],
440 args["space"]["advanced"]["integral_constraints"],
444
445 assert(space_.basis_list().size() == pressure_space_.basis_list().size());
446 for (int i = 0; i < int(pressure_space_.basis_list().size()); ++i)
447 {
449 space_.basis_list()[i].compute_quadrature(b_quad);
450 (*pressure_space_.bases)[i].set_quadrature([b_quad](quadrature::Quadrature &quad) { quad = b_quad; });
451 }
452
454 problem->setup_bc(
458 problem->setup_bc(
461
462 problem->setup_nodal_bc(
464 problem->setup_nodal_bc(
466
467 for (const int n_id : boundary_.dirichlet_nodes)
468 {
469 const int tag = mesh.get_node_id(n_id);
470 if (problem->is_nodal_dimension_dirichlet(n_id, tag, 0, solution_space_id_))
471 boundary_.boundary_nodes.push_back(n_id);
472 }
473
476
477 for (int i = prev_bases; i < space_.n_bases; ++i)
478 boundary_.boundary_nodes.push_back(i);
479
481
482 if (space_.n_bases <= args["solver"]["advanced"]["cache_size"])
484 else
486
488
489 logger().info("n pressure bases: {}", pressure_space_.n_bases);
490 }
491
493 {
494 igl::Timer timer;
495 json p_params = {};
496 p_params["formulation"] = primary_assembler_->name();
497 {
498 RowVectorNd min, max, delta;
499 mesh.bounding_box(min, max);
500 delta = (max - min) / 2. + min;
501 if (mesh.is_volume())
502 p_params["bbox_center"] = {delta(0), delta(1), delta(2)};
503 else
504 p_params["bbox_center"] = {delta(0), delta(1)};
505 }
506 problem->set_parameters(p_params, resources_);
507
508 rhs_.resize(0, 0);
509
510 timer.start();
511 logger().info("Assigning rhs...");
512
514 assert(rhs_assembler_ != nullptr);
515 rhs_assembler_->assemble(mass_assembler_->density(), rhs_);
516 rhs_ *= -1;
517
518 timings.assigning_rhs_time = timer.getElapsedTime();
519 logger().info(" took {}s", timings.assigning_rhs_time);
520
521 const int prev_size = rhs_.rows();
522 rhs_.conservativeResize(prev_size + pressure_space_.n_bases, rhs_.cols());
524 {
525 rhs_.bottomRows(pressure_space_.n_bases).setZero();
526 }
527 else
528 {
529 Eigen::MatrixXd tmp = Eigen::MatrixXd::Zero(pressure_space_.n_bases, 1);
531 const int gdiscr_order = mesh_->orders().size() <= 0 ? 1 : mesh_->orders().maxCoeff();
532 const QuadratureOrders boundary_samples = n_boundary_samples(space_.disc_orders.maxCoeff(), space_.disc_ordersq.maxCoeff(), gdiscr_order);
533 tmp_rhs_assembler->set_bc(
534 std::vector<mesh::LocalBoundary>(), std::vector<int>(), boundary_samples, boundary_.local_neumann_boundary, tmp);
535 rhs_.bottomRows(pressure_space_.n_bases) = tmp;
536 }
537 }
538
540 {
541 if (!problem->is_time_dependent())
542 {
543 avg_mass_ = 1;
545 return;
546 }
547
548 mass_.resize(0, 0);
549 igl::Timer timer;
550 timer.start();
551 logger().info("Assembling mass mat...");
553 avg_mass_ = 0;
554 for (int k = 0; k < mass_.outerSize(); ++k)
555 for (StiffnessMatrix::InnerIterator it(mass_, k); it; ++it)
556 {
557 assert(it.col() == k);
558 avg_mass_ += it.value();
559 }
560 avg_mass_ /= std::max(1, int(mass_.rows()));
561 if (args["solver"]["advanced"]["lump_mass_matrix"])
563 timer.stop();
564 timings.assembling_mass_mat_time = timer.getElapsedTime();
565 logger().info(" took {}s", timings.assembling_mass_mat_time);
566 stats.nn_zero = mass_.nonZeros();
567 stats.num_dofs = mass_.rows();
568 stats.mat_size = (long long)mass_.rows() * (long long)mass_.cols();
569 }
570
575
576 void BilaplacianVarForm::prepare_initial_solution(Eigen::MatrixXd &sol) const
577 {
578 if (sol.size() <= 0)
579 {
580 assert(rhs_assembler_ != nullptr);
581 if (problem->is_time_dependent())
582 rhs_assembler_->initial_solution(sol);
583 else
584 {
585 sol.resize(rhs_.size(), 1);
586 sol.setZero();
587 }
588 }
589 if (sol.cols() > 1)
590 sol.conservativeResize(Eigen::NoChange, 1);
591 sol.conservativeResize(stacked_ndof(), sol.cols());
592 sol.bottomRows(pressure_space_.n_bases).setZero();
593 }
594
595 void BilaplacianVarForm::split_solution(const Eigen::MatrixXd &stacked, Eigen::MatrixXd &primary, Eigen::MatrixXd &pressure) const
596 {
597 const int cols = std::max(1, int(stacked.cols()));
598 primary.setZero(space_.n_bases, cols);
599 pressure.setZero(pressure_space_.n_bases, cols);
600 const int primary_rows = std::min(space_.n_bases, int(stacked.rows()));
601 if (primary_rows > 0)
602 primary.topRows(primary_rows) = stacked.topRows(primary_rows);
603 if (stacked.rows() > space_.n_bases)
604 {
605 const int pressure_rows = std::min(pressure_space_.n_bases, int(stacked.rows()) - space_.n_bases);
606 if (pressure_rows > 0)
607 pressure.topRows(pressure_rows) = stacked.middleRows(space_.n_bases, pressure_rows);
608 }
609 }
610
612 {
613 igl::Timer timer;
614 timer.start();
615 logger().info("Assembling stiffness mat...");
616
617 StiffnessMatrix main_stiffness, mixed_stiffness, aux_stiffness;
618 primary_assembler_->assemble(mesh_->is_volume(), space_.n_bases, space_.basis_list(), space_.geometry_basis_list(), ass_vals_cache_, 0, main_stiffness);
621
623 space_.n_bases, pressure_space_.n_bases, 1, /*add_average=*/false,
624 main_stiffness, mixed_stiffness, aux_stiffness, stiffness);
625
626 timer.stop();
627 timings.assembling_stiffness_mat_time = timer.getElapsedTime();
628 logger().info(" took {}s", timings.assembling_stiffness_mat_time);
629 stats.nn_zero = stiffness.nonZeros();
630 stats.num_dofs = stiffness.rows();
631 stats.mat_size = (long long)stiffness.rows() * (long long)stiffness.cols();
632 write_matrix_market(args, stiffness);
633 }
634
636 const std::unique_ptr<polysolve::linear::Solver> &solver,
638 Eigen::VectorXd &b,
639 const bool compute_spectrum,
640 Eigen::MatrixXd &sol)
641 {
642 Eigen::VectorXd x;
643 stats.spectrum = dirichlet_solve(
644 *solver,
645 A,
646 b,
648 x,
650 args["output"]["data"]["stiffness_mat"],
651 compute_spectrum,
652 /*is_fluid=*/false,
653 /*use_avg_pressure=*/false);
654 sol = x;
655 solver->get_info(stats.solver_info);
656 }
657
659 {
660 auto solver = polysolve::linear::Solver::create(args["solver"]["linear"], logger());
661 logger().info("{}...", solver->name());
662 const int gdiscr_order = mesh_->orders().size() <= 0 ? 1 : mesh_->orders().maxCoeff();
663 const QuadratureOrders boundary_samples = n_boundary_samples(space_.disc_orders.maxCoeff(), space_.disc_ordersq.maxCoeff(), gdiscr_order);
664 rhs_assembler_->set_bc(
666 (primary_assembler_->name() != "Bilaplacian") ? boundary_.local_neumann_boundary : std::vector<mesh::LocalBoundary>(), rhs_);
669 Eigen::VectorXd b = rhs_;
670 solve_linear_system(solver, A, b, args["output"]["advanced"]["spectrum"], sol);
671 }
672
674 {
675 auto solver = polysolve::linear::Solver::create(args["solver"]["linear"], logger());
676 logger().info("{}...", solver->name());
677
678 Eigen::MatrixXd value, pressure;
679 split_solution(sol, value, pressure);
681 args["time"]["integrator"]);
682 bdf->init(
683 value,
684 Eigen::MatrixXd::Zero(value.rows(), value.cols()),
685 Eigen::MatrixXd::Zero(value.rows(), value.cols()),
686 dt);
687 time_integrator = bdf;
689 time_integrator, "/checkpoint/state/primary_integrator", dt,
690 "primary", space_, 1);
691
692 save_timestep(t0, 0, t0, dt, sol);
693
694 Eigen::MatrixXd current_rhs = rhs_;
695 StiffnessMatrix stiffness, expanded_mass;
696 build_stiffness_mat(stiffness);
697 expand_primary_matrix(stacked_ndof(), mass_, expanded_mass);
698 const int gdiscr_order = mesh_->orders().size() <= 0 ? 1 : mesh_->orders().maxCoeff();
699 const QuadratureOrders boundary_samples = n_boundary_samples(space_.disc_orders.maxCoeff(), space_.disc_ordersq.maxCoeff(), gdiscr_order);
700
701 for (int t = 1; t <= time_steps; ++t)
702 {
703 const double time = t0 + t * dt;
704 rhs_assembler_->compute_energy_grad(
706 current_rhs);
707 rhs_assembler_->set_bc(
708 boundary_.local_boundary, boundary_.boundary_nodes, boundary_samples, boundary_.local_neumann_boundary, current_rhs, value, time);
709
710 if (current_rhs.rows() != stacked_ndof())
711 {
712 const int old_rows = current_rhs.rows();
713 current_rhs.conservativeResize(stacked_ndof(), current_rhs.cols());
714 if (stacked_ndof() > old_rows)
715 current_rhs.bottomRows(stacked_ndof() - old_rows).setZero();
716 }
717 current_rhs.bottomRows(pressure_space_.n_bases).setZero();
718
719 StiffnessMatrix A = expanded_mass / bdf->beta_dt() + stiffness;
720 Eigen::VectorXd b = Eigen::VectorXd::Zero(stacked_ndof());
721 b.head(space_.n_bases) = (mass_ * bdf->weighted_sum_x_prevs()) / bdf->beta_dt();
722 for (int i : boundary_.boundary_nodes)
723 b[i] = 0;
724 b += current_rhs;
725
726 solve_linear_system(solver, A, b, args["output"]["advanced"]["spectrum"].get<bool>() && t == time_steps, sol);
727 split_solution(sol, value, pressure);
728 bdf->update_quantities(value.col(0));
729
730 save_timestep(time, t, t0, dt, sol);
731 save_step_state(t0, dt, t, sol, time_integrator.get());
732 logger().info("{}/{} t={}", t, time_steps, time);
734 }
735 }
736
738 Eigen::MatrixXd &sol,
739 const InitialConditionOverride *initial_condition_override,
740 const ForwardStepCallback &post_step)
741 {
742 assert(!initial_condition_override && "Bilaplacian does not support initial-condition overrides");
743 assert(!post_step && "Bilaplacian does not support post-step callbacks");
744
745 stats.spectrum.setZero();
746 igl::Timer timer;
747 timer.start();
748 logger().info("Solving {}", primary_assembler_->name());
750 if (problem->is_time_dependent())
752 else
753 {
754 time_integrator = nullptr;
756 }
757 timer.stop();
758 timings.solving_time = timer.getElapsedTime();
759 logger().info(" took {}s", timings.solving_time);
760 }
761
762 std::vector<io::OutputField> BilaplacianVarForm::output_fields(
763 const io::OutputSample &sample,
764 const Eigen::MatrixXd &solution,
765 const io::OutputFieldOptions &options) const
766 {
767 std::vector<io::OutputField> fields;
768 if (!mesh_ || !problem || solution.size() <= 0)
769 return fields;
770
771 Eigen::MatrixXd value, pressure;
772 split_solution(solution, value, pressure);
773
774 const bool has_element_samples = sample.local_points.rows() > 0 && sample.local_points.rows() == sample.element_ids.size();
775 const int output_rows = sample.points.rows() > 0 ? sample.points.rows() : std::max<int>(sample.local_points.rows(), sample.node_ids.size());
776 const int primary_ndof = std::min<int>(value.rows(), space_.n_bases);
777 const Eigen::MatrixXd primary_solution = value.topRows(primary_ndof);
778
779 const auto sample_dof_field = [&](const Eigen::MatrixXd &dof_values, Eigen::MatrixXd &values, Eigen::MatrixXd *gradients = nullptr) -> bool {
780 if (dof_values.size() <= 0)
781 return false;
782
783 if (has_element_samples)
784 {
785 values.resize(sample.local_points.rows(), 1);
786 if (gradients)
787 gradients->resize(sample.local_points.rows(), mesh_->dimension());
788 for (int i = 0; i < sample.local_points.rows(); ++i)
789 {
790 const int element_id = sample.element_ids(i);
791 if (element_id < 0)
792 {
793 values(i) = 0;
794 if (gradients)
795 gradients->row(i).setZero();
796 continue;
797 }
798
799 Eigen::MatrixXd local_sol, local_grad;
802 element_id, sample.local_points.row(i), dof_values, local_sol, local_grad);
803 values(i) = local_sol(0);
804 if (gradients)
805 gradients->row(i) = local_grad;
806 }
807
808 if (output_rows > values.rows())
809 {
810 const int previous_rows = values.rows();
811 values.conservativeResize(output_rows, Eigen::NoChange);
812 values.bottomRows(output_rows - previous_rows).setZero();
813 if (gradients)
814 {
815 gradients->conservativeResize(output_rows, Eigen::NoChange);
816 gradients->bottomRows(output_rows - previous_rows).setZero();
817 }
818 }
819 return true;
820 }
821
822 if (sample.node_ids.size() > 0)
823 {
824 values.resize(sample.node_ids.size(), 1);
825 for (int i = 0; i < sample.node_ids.size(); ++i)
826 {
827 const int node_id = sample.node_ids(i);
828 if (node_id < 0 || node_id >= dof_values.rows())
829 return false;
830 values(i) = dof_values(node_id);
831 }
832 return sample.points.rows() == 0 || sample.points.rows() == values.rows();
833 }
834
835 return false;
836 };
837
838 const auto &paraview_options = args["output"]["paraview"]["options"];
839 if (has_element_samples && problem->has_exact_sol() && sample.points.rows() == output_rows)
840 {
841 Eigen::MatrixXd exact;
842 problem->exact(sample.points, sample.time, exact);
843 if (exact.rows() == output_rows)
844 {
845 if (options.export_field("exact"))
846 fields.push_back({"exact", exact, io::OutputField::Association::Point});
847 if (options.export_field("error"))
848 {
849 Eigen::MatrixXd values;
850 if (sample_dof_field(primary_solution, values))
851 fields.push_back({"error", (values - exact).rowwise().norm(), io::OutputField::Association::Point});
852 }
853 }
854 }
855
856 if ((paraview_options["nodes"] || (!options.fields.empty() && options.export_field("nodes")))
857 && has_element_samples
858 && sample.primitive_ids.size() == 0)
859 {
860 Eigen::MatrixXd dof_ids(primary_ndof, 1);
861 dof_ids.col(0).setLinSpaced(primary_ndof, 0, primary_ndof - 1);
862 Eigen::MatrixXd values;
863 if (sample_dof_field(dof_ids, values))
864 fields.push_back({"nodes", values, io::OutputField::Association::Point});
865 }
866
867 if ((paraview_options["jacobian_validity"] || (!options.fields.empty() && options.export_field("validity")))
868 && has_element_samples
869 && mesh_->dimension() == 1
870 && sample.primitive_ids.size() == 0)
871 {
872 const auto invalid_elements = utils::count_invalid(mesh_->dimension(), space_.basis_list(), space_.geometry_basis_list(), primary_solution);
873 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
874 for (int i = 0; i < sample.element_ids.size(); ++i)
875 validity(i) = std::find(invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i)) != invalid_elements.end();
876 fields.push_back({"validity", validity, io::OutputField::Association::Point});
877 }
878
879 const bool export_solution_gradient =
880 !options.fields.empty() && options.export_field("solution_gradient");
881 if (options.export_field("solution") || export_solution_gradient)
882 {
883 Eigen::MatrixXd values, gradients;
884 if (sample_dof_field(
885 value, values,
886 export_solution_gradient ? &gradients : nullptr))
887 {
888 if (options.export_field("solution"))
889 fields.push_back({"solution", values, io::OutputField::Association::Point});
890 if (export_solution_gradient)
891 fields.push_back({"solution_gradient", gradients, io::OutputField::Association::Point});
892 }
893 }
894
895 if (paraview_options["material"] && has_element_samples)
896 {
897 const auto &params = primary_assembler_->parameters();
898 std::map<std::string, Eigen::MatrixXd> param_values;
899 for (const auto &[p, _] : params)
900 param_values[p].setZero(output_rows, 1);
901
902 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
903 const auto &density = mass_assembler_->density();
904 for (int i = 0; i < sample.local_points.rows(); ++i)
905 {
906 const int element_id = sample.element_ids(i);
907 if (element_id < 0)
908 continue;
909
910 for (const auto &[p, func] : params)
911 param_values.at(p)(i) = func(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
912 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
913 }
914
915 for (const auto &[name, values] : param_values)
916 if (options.export_field(name))
917 fields.push_back({name, values, io::OutputField::Association::Point});
918 if (options.export_field("rho"))
919 fields.push_back({"rho", rhos, io::OutputField::Association::Point});
920 }
921
922 if (paraview_options["body_ids"] && options.export_field("body_ids") && has_element_samples)
923 {
924 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
925 for (int i = 0; i < sample.element_ids.size(); ++i)
926 {
927 const int element_id = sample.element_ids(i);
928 if (element_id >= 0)
929 ids(i) = mesh_->get_body_id(element_id);
930 }
931 fields.push_back({"body_ids", ids, io::OutputField::Association::Point});
932 }
933
934 const bool export_pressure_gradient =
935 !options.fields.empty() && options.export_field("pressure_gradient");
936 if (mesh_ && (options.export_field("pressure") || export_pressure_gradient || (!options.fields.empty() && options.export_field("auxiliary"))))
937 {
938 Eigen::MatrixXd values, gradients;
940 *mesh_, pressure_space_.basis_list(), space_.geometry_basis_list(), sample, pressure, values,
941 export_pressure_gradient ? &gradients : nullptr))
942 {
943 if (options.export_field("pressure"))
944 fields.push_back({"pressure", values, io::OutputField::Association::Point});
945 if (export_pressure_gradient)
946 fields.push_back({"pressure_gradient", gradients, io::OutputField::Association::Point});
947 if (!options.fields.empty() && options.export_field("auxiliary"))
948 fields.push_back({"auxiliary", values, io::OutputField::Association::Point});
949 }
950 }
951 return fields;
952 }
953} // namespace polyfem::varform
int x
std::array< Matrix< int, 3, 3 >, 3 > space_
static std::shared_ptr< MixedAssembler > make_mixed_assembler(const std::string &formulation)
static std::string other_assembler_name(const std::string &formulation)
static void merge_mixed_matrices(const int n_bases, const int n_pressure_bases, const int problem_dim, const bool add_average, const StiffnessMatrix &velocity_stiffness, const StiffnessMatrix &mixed_stiffness, const StiffnessMatrix &pressure_stiffness, StiffnessMatrix &stiffness)
utility to merge 3 blocks of mixed matrices, A=velocity_stiffness, B=mixed_stiffness,...
static std::shared_ptr< Assembler > make_assembler(const std::string &formulation)
Caches basis evaluation and geometric mapping at every element.
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 assembling_stiffness_mat_time
time to assembly
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: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
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.
std::string name() const override
Get the name of the variational formulation.
void save_json(const Eigen::MatrixXd &solution, std::ostream &out) const override
Save the solution to a JSON file, for output purposes.
io::OutStatsData compute_errors(const Eigen::MatrixXd &solution) override
Get the error statistics of the variational formulation, for output purposes.
void build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args) override
std::shared_ptr< time_integrator::ImplicitTimeIntegrator > time_integrator
assembler::AssemblyValsCache mass_ass_vals_cache_
std::shared_ptr< assembler::MixedAssembler > mixed_assembler_
void solve_static_linear(Eigen::MatrixXd &sol)
std::shared_ptr< assembler::Mass > mass_assembler_
void deserialize_checkpoint(const io::CheckpointReader &reader, Eigen::MatrixXd &solution) override
std::shared_ptr< assembler::HRZMass > pure_mass_assembler_
assembler::AssemblyValsCache pure_mass_ass_vals_cache_
void assemble_mass_mat(const mesh::Mesh &mesh, const json &args) override
void assemble_rhs(const mesh::Mesh &mesh) override
void solve_transient_linear(Eigen::MatrixXd &sol)
assembler::AssemblyValsCache ass_vals_cache_
void load_mesh(const mesh::Mesh &mesh, 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.
void build_stiffness_mat(StiffnessMatrix &stiffness)
void prepare_initial_solution(Eigen::MatrixXd &sol) const
void export_data(const Eigen::MatrixXd &solution) const override
std::shared_ptr< assembler::Assembler > primary_assembler_
io::OutputSpace output_space() const override
Get the output space of the variational formulation, for output purposes.
assembler::AssemblyValsCache pressure_ass_vals_cache_
void solve_problem(Eigen::MatrixXd &sol, const InitialConditionOverride *initial_condition_override, const ForwardStepCallback &post_step) override
void split_solution(const Eigen::MatrixXd &stacked, Eigen::MatrixXd &primary, Eigen::MatrixXd &pressure) const
void solve_linear_system(const std::unique_ptr< polysolve::linear::Solver > &solver, StiffnessMatrix &A, Eigen::VectorXd &b, const bool compute_spectrum, Eigen::MatrixXd &sol)
std::shared_ptr< assembler::Assembler > pressure_assembler_
std::shared_ptr< assembler::RhsAssembler > rhs_assembler_
void serialize_checkpoint(io::CheckpointWriter &writer, const Eigen::MatrixXd &solution, const io::CheckpointMetadata &metadata) const override
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.
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
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
std::map< int, basis::InterfaceData > poly_edge_to_data
Polygonal-basis construction data, indexed by element ID.
Definition FESpace.hpp:77
const std::vector< basis::ElementBases > & basis_list() const
Definition FESpace.hpp:109
bool is_iso_parametric() const
Definition FESpace.hpp:104
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 save_step_state(const double t0, const double dt, const int t, const Eigen::MatrixXd &solution, const time_integrator::ImplicitTimeIntegrator *time_integrator) const
Definition VarForm.cpp:863
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
void notify_time_step(const int t, const int time_steps, const double t0, const double dt) const
Definition VarForm.cpp:1105
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
void restore_checkpoint_integrator(const std::shared_ptr< time_integrator::ImplicitTimeIntegrator > &integrator, const std::string &group, double dt, const std::string &ordering, const FESpace &space, int block_size) const
Definition VarForm.cpp:917
virtual void serialize_checkpoint(io::CheckpointWriter &writer, const Eigen::MatrixXd &solution, const io::CheckpointMetadata &metadata) const
Definition VarForm.cpp:897
QuadratureOrders n_boundary_samples(const int discr_order, const int discr_orderq, const int gdiscr_order) const
Definition VarForm.cpp:185
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
void save_timestep(const double time, const int t, const double t0, const double dt, const Eigen::MatrixXd &solution) const
Definition VarForm.cpp:1047
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)
bool sample_scalar_field(const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &field_bases, const std::vector< basis::ElementBases > &gbases, const io::OutputSample &sample, const Eigen::MatrixXd &dof_values, Eigen::MatrixXd &values, Eigen::MatrixXd *gradients=nullptr)
void expand_primary_matrix(const int full_size, const StiffnessMatrix &primary, StiffnessMatrix &expanded)
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