PolyFEM
Loading...
Searching...
No Matches
OperatorSplittingVarForm.cpp
Go to the documentation of this file.
2
17
18#include <igl/Timer.h>
19
20namespace polyfem::varform
21{
24 const Eigen::MatrixXd &solution,
25 const io::CheckpointMetadata &metadata) const
26 {
27 VarForm::serialize_checkpoint(writer, solution, metadata);
28 write_checkpoint_ordering(writer, "primary", space_);
29 write_checkpoint_ordering(writer, "pressure", pressure_space_);
30 Eigen::MatrixXd velocity, pressure;
31 split_solution(solution, velocity, pressure);
32 writer.write_matrix("/checkpoint/state/velocity", velocity);
33 writer.write_matrix("/checkpoint/state/pressure", pressure);
34 }
35
37 const io::CheckpointReader &reader,
38 Eigen::MatrixXd &solution)
39 {
40 VarForm::deserialize_checkpoint(reader, solution);
42 for (const std::string &path : {
43 "/checkpoint/state/velocity", "/checkpoint/state/pressure"})
44 if (!reader.exists(path))
45 log_and_throw_error("Operator-splitting checkpoint is missing {}.", path);
46
47 Eigen::MatrixXd velocity = reader.read_matrix("/checkpoint/state/velocity");
48 Eigen::MatrixXd pressure = reader.read_matrix("/checkpoint/state/pressure");
49 if (velocity.rows() != primary_ndof() || velocity.cols() != 1)
50 log_and_throw_error("Operator-splitting checkpoint velocity has invalid dimensions.");
51 if (pressure.rows() != pressure_space_.n_bases || pressure.cols() != 1)
52 log_and_throw_error("Operator-splitting checkpoint pressure has invalid dimensions.");
53 if (!solution.topRows(primary_ndof()).isApprox(velocity)
54 || !solution.middleRows(primary_ndof(), pressure_space_.n_bases).isApprox(pressure))
55 log_and_throw_error("Operator-splitting checkpoint contains inconsistent solution blocks.");
56
57 reorder_checkpoint_block(reader, "primary", space_, mesh_->dimension(), 0, velocity);
58 reorder_checkpoint_block(reader, "pressure", pressure_space_, 1, 0, pressure);
59 solution.topRows(primary_ndof()) = velocity;
60 solution.middleRows(primary_ndof(), pressure_space_.n_bases) = pressure;
61 }
62
63 using namespace varform::internal;
64
66 {
68 space_.reset();
75 rhs_assembler_ = nullptr;
76 mass_.resize(0, 0);
77 rhs_.resize(0, 0);
78 primary_assembler_ = nullptr;
79 mass_assembler_ = nullptr;
80 mixed_assembler_ = nullptr;
81 pressure_assembler_ = nullptr;
82 use_avg_pressure = true;
83 avg_mass_ = 0;
84 t0 = 0;
85 time_steps = 0;
86 dt = 0;
87 }
88
89 void OperatorSplittingVarForm::init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path)
90 {
91 for (const std::string field : {"pressure_boundary", "pressure_cavity"})
92 {
93 const json::json_pointer path("/boundary_conditions/" + field);
94 if (args.contains(path) && !args.at(path).empty())
95 log_and_throw_error("{} does not support boundary_conditions.{}.", formulation, field);
96 }
97
98 VarForm::init(formulation, units, args, out_path);
99 const bool is_time_dependent = args.contains("time") && !args["time"].is_null();
100
102 mass_assembler_ = std::make_shared<assembler::Mass>();
105
106 if (!args.contains("preset_problem"))
107 {
108 problem = std::make_shared<assembler::GenericTensorProblem>("GenericTensor");
109 problem->clear();
110
111 json tmp;
112 tmp["is_time_dependent"] = is_time_dependent;
113 problem->set_parameters(tmp, resources_);
114
115 auto bc = args["boundary_conditions"];
116 problem->set_parameters(bc, resources_);
117 problem->set_parameters(args["initial_conditions"], resources_);
118 problem->set_parameters(args["output"], resources_);
119 }
120 else
121 {
122 if (args["preset_problem"]["type"] == "Kernel")
123 {
124 problem = std::make_shared<problem::KernelProblem>("Kernel", *primary_assembler_);
125 problem->clear();
126 }
127 else
128 {
129 problem = problem::ProblemFactory::factory().get_problem(args["preset_problem"]["type"]);
130 problem->clear();
131 }
132 problem->set_parameters(args["preset_problem"], resources_);
133 }
134
135 problem->set_units(*primary_assembler_, units);
136
137 t0 = is_time_dependent ? args["time"]["t0"].get<double>() : 0.0;
138 time_steps = is_time_dependent ? args["time"]["time_steps"].get<int>() : 0;
139 dt = is_time_dependent ? args["time"]["dt"].get<double>() : 0.0;
140
141 assert(primary_assembler_);
142 assert(primary_assembler_->name() == "OperatorSplitting");
143 }
144
145 void OperatorSplittingVarForm::save_json(const Eigen::MatrixXd &solution, std::ostream &out) const
146 {
147 if (!mesh_)
148 {
149 logger().error("Load the mesh first!");
150 return;
151 }
152 if (solution.size() <= 0)
153 {
154 logger().error("Solve the problem first!");
155 return;
156 }
157
158 logger().info("Saving json...");
159 const int primary_size = primary_ndof();
160 const Eigen::MatrixXd stats_solution =
161 solution.rows() >= primary_size
162 ? solution.topRows(primary_size).eval()
163 : solution;
164
165 nlohmann::json j;
168 stats_solution, *mesh_, space_.disc_orders, space_.disc_ordersq, *problem,
170 args["output"]["advanced"]["sol_at_node"], j);
171 out << j.dump(4) << std::endl;
172 }
173
175 {
176 Eigen::VectorXi output_orders = space_.disc_orders;
177 if (mesh_ && space_.disc_ordersq.size() == space_.disc_orders.size())
178 {
179 for (int e = 0; e < output_orders.size(); ++e)
180 {
181 if (mesh_->is_prism(e))
182 output_orders(e) = std::max(space_.disc_orders(e), space_.disc_ordersq(e));
183 }
184 }
185
186 return {
187 mesh_.get(),
189 output_orders,
190 &space_.polys,
193 nullptr,
194 nullptr,
197 }
198
200 {
201 if (!args["output"]["advanced"]["compute_error"])
202 return stats;
203
204 double tend = 0;
205 if (args.contains("time") && !args["time"].is_null())
206 {
207 if (args["time"].contains("tend"))
208 tend = args["time"]["tend"].get<double>();
209 else
210 tend = t0 + dt * time_steps;
211 }
212
213 Eigen::MatrixXd velocity, pressure;
214 split_solution(solution, velocity, pressure);
216 return stats;
217 }
218
219 void OperatorSplittingVarForm::export_data(const Eigen::MatrixXd &solution) const
220 {
221 const io::OutputSpace space = output_space();
222 if (!space.mesh)
223 {
224 logger().error("Load the mesh first!");
225 return;
226 }
227 if (solution.size() <= 0)
228 {
229 logger().error("Solve the problem first!");
230 return;
231 }
232
234
235 const std::string vis_mesh_path = resolve_output_path(args["output"]["paraview"]["file_name"]);
236 const bool has_time = args.contains("time") && !args["time"].is_null();
237 double tend = 1.0;
238 if (has_time)
239 {
240 if (args["time"].contains("tend"))
241 tend = args["time"]["tend"].get<double>();
242 else
243 tend = t0 + dt * time_steps;
244 }
245 double local_dt = 1;
246 if (has_time)
247 local_dt = args["time"]["dt"];
248
249 const auto opts = export_options(space);
251 space,
252 output_field_function(solution, opts),
253 has_time,
254 tend, local_dt,
255 opts,
256 vis_mesh_path);
257
258 Eigen::MatrixXd velocity, pressure;
259 split_solution(solution, velocity, pressure);
260
261 const std::string solution_path = resolve_output_path(args["output"]["data"]["solution"]);
262 if (!solution_path.empty())
263 {
264 const int primary_rows = std::min<int>(velocity.rows(), primary_ndof());
265 const Eigen::MatrixXd primary_solution = velocity.topRows(primary_rows);
266 if (opts.reorder_output && space_.space_in_node_to_node.size() > 0)
267 {
268 const Eigen::MatrixXd nodal_solution = utils::unflatten(primary_solution, mesh_->dimension());
269 Eigen::MatrixXd reordered = Eigen::MatrixXd::Zero(nodal_solution.rows(), nodal_solution.cols());
270 for (int input_node = 0; input_node < space_.space_in_node_to_node.size(); ++input_node)
271 {
272 const int node = space_.space_in_node_to_node(input_node);
273 if (node >= 0 && node < nodal_solution.rows() && input_node < reordered.rows())
274 reordered.row(input_node) = nodal_solution.row(node);
275 }
276 io::write_matrix(solution_path, reordered);
277 }
278 else
279 {
280 io::write_matrix(solution_path, primary_solution);
281 }
282 }
283
284 const std::string nodes_path = resolve_output_path(args["output"]["data"]["nodes"]);
285 if (!nodes_path.empty())
286 {
287 Eigen::MatrixXd nodes = Eigen::MatrixXd::Zero(space_.n_bases, mesh_->dimension());
288 for (const basis::ElementBases &element_bases : space_.basis_list())
289 for (const basis::Basis &basis : element_bases.bases)
290 for (const auto &global : basis.global())
291 nodes.row(global.index) = global.node;
292 io::write_matrix(nodes_path, nodes);
293 }
294
295 const std::string stress_path = resolve_output_path(args["output"]["data"]["stress_mat"]);
296 const std::string mises_path = resolve_output_path(args["output"]["data"]["mises"]);
297 if ((!stress_path.empty() || !mises_path.empty()) && primary_assembler_)
298 {
299 Eigen::MatrixXd stress;
300 Eigen::VectorXd mises;
304 stress, mises);
305 if (!stress_path.empty())
306 io::write_matrix(stress_path, stress);
307 if (!mises_path.empty())
308 io::write_matrix(mises_path, mises);
309 }
310 }
311
313 {
314 (void)args;
317 problem->init(mesh);
318
320 mixed_assembler_->set_size(mesh.dimension());
323 }
324
326 {
327 return mesh_ ? space_.n_bases * mesh_->dimension() : 0;
328 }
329
334
339
341 {
342 json rhs_solver_params = args["solver"]["linear"];
343 if (!rhs_solver_params.contains("Pardiso"))
344 rhs_solver_params["Pardiso"] = {};
345 rhs_solver_params["Pardiso"]["mtype"] = -2;
346
347 rhs_assembler_ = std::make_shared<assembler::RhsAssembler>(
348 *primary_assembler_, *mesh_, nullptr,
352 args["space"]["advanced"]["bc_method"],
353 rhs_solver_params,
354 /*fe_space_id=*/-1);
355 }
356
357 void OperatorSplittingVarForm::build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
358 {
359 assert(problem);
360 assert(primary_assembler_);
361 assert(mass_assembler_);
362
363 Eigen::VectorXi space_disc_orders, space_disc_ordersq;
364 assign_discr_orders(args["space"], mesh, space_disc_orders, space_disc_ordersq);
365
366 if (args["space"]["use_p_ref"])
367 {
369 mesh,
370 args["space"]["advanced"]["B"],
371 args["space"]["advanced"]["h1_formula"],
372 args["space"]["discr_order"],
373 args["space"]["advanced"]["discr_order_max"],
374 stats,
375 space_disc_orders);
376
377 logger().info("min p: {} max p: {}", space_disc_orders.minCoeff(), space_disc_orders.maxCoeff());
378 }
379
381 mesh,
382 iso_parametric,
383 space_disc_orders,
384 space_disc_ordersq,
385 args["space"]["basis_type"],
386 args["space"]["poly_basis_type"],
388 mesh.dimension(),
389 args["space"]["advanced"]["quadrature_order"],
390 args["space"]["advanced"]["mass_quadrature_order"],
391 args["space"]["advanced"]["use_corner_quadrature"],
392 args["space"]["advanced"]["n_harmonic_samples"],
393 args["space"]["advanced"]["integral_constraints"],
394 space_,
395 boundary_);
396
397 problem->update_nodes(space_.space_in_node_to_node);
399
400 const auto &current_bases = space_.geometry_basis_list();
401 if (args["space"]["advanced"]["count_flipped_els"])
402 stats.count_flipped_elements(mesh, current_bases);
403
404 const int n_samples = 10;
405 stats.compute_mesh_size(mesh, current_bases, n_samples, args["output"]["advanced"]["curved_mesh_size"]);
406
407 logger().info("flipped elements {}", stats.n_flipped);
408 logger().info("h: {}", stats.mesh_size);
409
410 if (space_.disc_orders.maxCoeff() != space_.disc_orders.minCoeff())
411 log_and_throw_error("p refinement not supported in operator splitting!");
412 if (!space_.poly_edge_to_data.empty())
413 log_and_throw_error("Polygonal bases are not supported in operator splitting!");
414
415 if (space_.n_bases <= args["solver"]["advanced"]["cache_size"])
416 {
417 igl::Timer cache_timer;
418 cache_timer.start();
419 logger().info("Building cache...");
420 ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases);
421 mass_ass_vals_cache_.init(mesh.is_volume(), space_.basis_list(), current_bases, true);
422 logger().info(" took {}s", cache_timer.getElapsedTime());
423 }
424 else
425 {
428 }
429
430 const auto &all_boundary = boundary_.total_local_boundary;
431 const int prev_bases = space_.n_bases;
432 const int prev_b_size = int(all_boundary.size());
433 const bool use_corner_quadrature = args["space"]["advanced"]["use_corner_quadrature"];
434 const int quadrature_order = args["space"]["advanced"]["quadrature_order"].get<int>();
435 const int mass_quadrature_order = args["space"]["advanced"]["mass_quadrature_order"].get<int>();
436 Eigen::VectorXi pressure_disc_orders, pressure_disc_ordersq;
437 assign_discr_orders(args["space"], mesh, pressure_disc_orders, pressure_disc_ordersq);
438 const std::string pressure_basis_type = args["space"]["basis_type"].get<std::string>() == "Bernstein" ? "Bernstein" : "Lagrange";
440 mesh,
441 /*iso_parametric=*/true,
442 pressure_disc_orders,
443 pressure_disc_ordersq,
444 pressure_basis_type,
445 args["space"]["poly_basis_type"],
447 /*value_dim=*/1,
448 quadrature_order,
449 mass_quadrature_order,
450 use_corner_quadrature,
451 args["space"]["advanced"]["n_harmonic_samples"],
452 args["space"]["advanced"]["integral_constraints"],
456
457 assert(space_.basis_list().size() == pressure_space_.basis_list().size());
458 for (int i = 0; i < int(pressure_space_.basis_list().size()); ++i)
459 {
461 space_.basis_list()[i].compute_quadrature(b_quad);
462 (*pressure_space_.bases)[i].set_quadrature([b_quad](quadrature::Quadrature &quad) { quad = b_quad; });
463 }
464
466 problem->setup_bc(
467 mesh, assembler::BoundaryKind::Dirichlet, /*fe_space_id=*/-1,
470 problem->setup_bc(
471 mesh, assembler::BoundaryKind::Neumann, /*fe_space_id=*/-1,
473
474 problem->setup_nodal_bc(
476 problem->setup_nodal_bc(
478
479 for (const int n_id : boundary_.dirichlet_nodes)
480 {
481 const int tag = mesh.get_node_id(n_id);
482 for (int d = 0; d < mesh.dimension(); ++d)
483 if (problem->is_nodal_dimension_dirichlet(n_id, tag, d, /*fe_space_id=*/-1))
484 boundary_.boundary_nodes.push_back(n_id * mesh.dimension() + d);
485 }
486
489
490 const bool has_neumann = !boundary_.local_neumann_boundary.empty() || int(boundary_.local_boundary.size()) < prev_b_size;
491 use_avg_pressure = !has_neumann;
492
493 for (int i = prev_bases; i < space_.n_bases; ++i)
494 for (int d = 0; d < mesh.dimension(); ++d)
495 boundary_.boundary_nodes.push_back(i * mesh.dimension() + d);
496
498
499 if (space_.n_bases <= args["solver"]["advanced"]["cache_size"])
501 else
503
505
506 logger().info("n pressure bases: {}", pressure_space_.n_bases);
507 }
508
510 {
512
513 igl::Timer timer;
514 json p_params = {};
515 p_params["formulation"] = primary_assembler_->name();
516 {
517 RowVectorNd min, max, delta;
518 mesh.bounding_box(min, max);
519 delta = (max - min) / 2. + min;
520 if (mesh.is_volume())
521 p_params["bbox_center"] = {delta(0), delta(1), delta(2)};
522 else
523 p_params["bbox_center"] = {delta(0), delta(1)};
524 }
525 problem->set_parameters(p_params, resources_);
526
527 rhs_.resize(0, 0);
528
529 timer.start();
530 logger().info("Assigning rhs...");
531
532 assert(rhs_assembler_ != nullptr);
533 rhs_assembler_->assemble(mass_assembler_->density(), rhs_);
534 rhs_ *= -1;
535
536 const int prev_size = rhs_.rows();
537 rhs_.conservativeResize(prev_size + pressure_block_size(), rhs_.cols());
538 rhs_.bottomRows(pressure_block_size()).setZero();
539
540 timer.stop();
541 timings.assigning_rhs_time = timer.getElapsedTime();
542 logger().info(" took {}s", timings.assigning_rhs_time);
543 }
544
546 {
547 (void)mesh;
548 (void)args;
549 mass_.resize(0, 0);
550 avg_mass_ = 1;
552 }
553
555 {
556 if (sol.size() <= 0)
557 {
558 assert(rhs_assembler_ != nullptr);
559 if (problem->is_time_dependent())
560 rhs_assembler_->initial_solution(sol);
561 else
562 {
563 sol.resize(rhs_.size(), 1);
564 sol.setZero();
565 }
566 }
567 if (sol.cols() > 1)
568 sol.conservativeResize(Eigen::NoChange, 1);
569 sol.conservativeResize(stacked_ndof(), sol.cols());
570 sol.bottomRows(pressure_block_size()).setZero();
571 }
572
573 void OperatorSplittingVarForm::split_solution(const Eigen::MatrixXd &stacked, Eigen::MatrixXd &primary, Eigen::MatrixXd &pressure) const
574 {
575 const int cols = std::max(1, int(stacked.cols()));
576 primary.setZero(primary_ndof(), cols);
577 pressure.setZero(pressure_space_.n_bases, cols);
578
579 const int primary_rows = std::min(primary_ndof(), int(stacked.rows()));
580 if (primary_rows > 0)
581 primary.topRows(primary_rows) = stacked.topRows(primary_rows);
582
583 if (stacked.rows() > primary_ndof())
584 {
585 const int pressure_rows = std::min(pressure_space_.n_bases, int(stacked.rows()) - primary_ndof());
586 if (pressure_rows > 0)
587 pressure.topRows(pressure_rows) = stacked.middleRows(primary_ndof(), pressure_rows);
588 }
589 }
590
591 void OperatorSplittingVarForm::stack_solution(const Eigen::MatrixXd &primary, const Eigen::MatrixXd &pressure, Eigen::MatrixXd &stacked) const
592 {
593 const int cols = std::max(primary.cols(), pressure.cols());
594 stacked.resize(stacked_ndof(), cols);
595 stacked.setZero();
596
597 const int primary_rows = std::min(primary_ndof(), int(primary.rows()));
598 if (primary_rows > 0)
599 stacked.topRows(primary_rows) = primary.topRows(primary_rows);
600
601 const int pressure_rows = std::min(pressure_space_.n_bases, int(pressure.rows()));
602 if (pressure_rows > 0)
603 stacked.middleRows(primary_ndof(), pressure_rows) = pressure.topRows(pressure_rows);
604 }
605
606 std::vector<io::OutputField> OperatorSplittingVarForm::output_fields(
607 const io::OutputSample &sample,
608 const Eigen::MatrixXd &solution,
609 const io::OutputFieldOptions &options) const
610 {
611 std::vector<io::OutputField> fields;
612 if (!mesh_ || !problem || solution.size() <= 0)
613 return fields;
614
615 Eigen::MatrixXd velocity, pressure;
616 split_solution(solution, velocity, pressure);
617
618 const int field_dim = mesh_->dimension();
619 const bool has_element_samples = sample.local_points.rows() > 0 && sample.local_points.rows() == sample.element_ids.size();
620 const int output_rows = sample.points.rows() > 0 ? sample.points.rows() : std::max<int>(sample.local_points.rows(), sample.node_ids.size());
621 const bool export_solution_gradient =
622 !options.fields.empty() && options.export_field("solution_gradient");
623 const bool export_pressure_gradient =
624 !options.fields.empty() && options.export_field("pressure_gradient");
625
626 const auto resize_to_output_rows = [&](Eigen::MatrixXd &values) {
627 if (output_rows <= values.rows())
628 return;
629
630 const int previous_rows = values.rows();
631 values.conservativeResize(output_rows, values.cols());
632 values.bottomRows(output_rows - previous_rows).setZero();
633 };
634
635 const auto sample_vector_field = [&](const Eigen::MatrixXd &dof_values, Eigen::MatrixXd &values, Eigen::MatrixXd *gradients = nullptr) -> bool {
636 if (dof_values.size() <= 0 || field_dim <= 0)
637 return false;
638
639 if (has_element_samples)
640 {
641 values.resize(sample.local_points.rows(), field_dim);
642 if (gradients)
643 gradients->resize(sample.local_points.rows(), field_dim * mesh_->dimension());
644 for (int i = 0; i < sample.local_points.rows(); ++i)
645 {
646 const int element_id = sample.element_ids(i);
647 if (element_id < 0)
648 {
649 values.row(i).setZero();
650 if (gradients)
651 gradients->row(i).setZero();
652 continue;
653 }
654
655 Eigen::MatrixXd local_sol, local_grad;
658 element_id, sample.local_points.row(i), dof_values, local_sol, local_grad);
659
660 for (int d = 0; d < field_dim; ++d)
661 values(i, d) = local_sol(d);
662 if (gradients)
663 gradients->row(i) = local_grad;
664 }
665
666 resize_to_output_rows(values);
667 if (gradients)
668 resize_to_output_rows(*gradients);
669 return true;
670 }
671
672 if (sample.node_ids.size() > 0)
673 {
674 values.resize(sample.node_ids.size(), field_dim);
675 for (int i = 0; i < sample.node_ids.size(); ++i)
676 {
677 const int node_id = sample.node_ids(i);
678 for (int d = 0; d < field_dim; ++d)
679 {
680 const int dof = node_id * field_dim + d;
681 if (dof < 0 || dof >= dof_values.rows())
682 return false;
683 values(i, d) = dof_values(dof);
684 }
685 }
686 return sample.points.rows() == 0 || sample.points.rows() == values.rows();
687 }
688
689 return false;
690 };
691
692 Eigen::MatrixXd velocity_values, velocity_gradients;
693 const bool sampled_velocity = sample_vector_field(
694 velocity, velocity_values,
695 export_solution_gradient ? &velocity_gradients : nullptr);
696 if (sampled_velocity && options.export_field("velocity"))
697 fields.push_back({"velocity", velocity_values, io::OutputField::Association::Point});
698 if (sampled_velocity && options.export_field("solution"))
699 fields.push_back({"solution", velocity_values, io::OutputField::Association::Point});
700 if (sampled_velocity && export_solution_gradient)
701 fields.push_back({"solution_gradient", velocity_gradients, io::OutputField::Association::Point});
702
703 if (mesh_ && (options.export_field("pressure") || export_pressure_gradient))
704 {
705 Eigen::MatrixXd values, gradients;
706 if (sample_scalar_field(
707 *mesh_, pressure_space_.basis_list(), space_.geometry_basis_list(), sample, pressure, values,
708 export_pressure_gradient ? &gradients : nullptr))
709 {
710 if (options.export_field("pressure"))
711 fields.push_back({"pressure", values, io::OutputField::Association::Point});
712 if (export_pressure_gradient)
713 fields.push_back({"pressure_gradient", gradients, io::OutputField::Association::Point});
714 }
715 }
716
717 const auto &paraview_options = args["output"]["paraview"]["options"];
718 if (paraview_options["material"] && has_element_samples)
719 {
720 const auto &params = primary_assembler_->parameters();
721 std::map<std::string, Eigen::MatrixXd> param_values;
722 for (const auto &[p, _] : params)
723 param_values[p].setZero(output_rows, 1);
724
725 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
726 const auto &density = mass_assembler_->density();
727 for (int i = 0; i < sample.local_points.rows(); ++i)
728 {
729 const int element_id = sample.element_ids(i);
730 if (element_id < 0)
731 continue;
732
733 for (const auto &[p, func] : params)
734 param_values.at(p)(i) = func(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
735 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
736 }
737
738 for (const auto &[field_name, values] : param_values)
739 if (options.export_field(field_name))
740 fields.push_back({field_name, values, io::OutputField::Association::Point});
741 if (options.export_field("rho"))
742 fields.push_back({"rho", rhos, io::OutputField::Association::Point});
743 }
744
745 if (paraview_options["body_ids"] && options.export_field("body_ids") && has_element_samples)
746 {
747 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
748 for (int i = 0; i < sample.element_ids.size(); ++i)
749 {
750 const int element_id = sample.element_ids(i);
751 if (element_id >= 0)
752 ids(i) = mesh_->get_body_id(element_id);
753 }
754 fields.push_back({"body_ids", ids, io::OutputField::Association::Point});
755 }
756
757 return fields;
758 }
759
760 void OperatorSplittingVarForm::solve_problem(
761 Eigen::MatrixXd &sol,
762 const InitialConditionOverride *initial_condition_override,
763 const ForwardStepCallback &post_step)
764 {
765 assert(!initial_condition_override && "Operator splitting does not support initial-condition overrides");
766 assert(!post_step && "Operator splitting does not support post-step callbacks");
767
768 stats.spectrum.setZero();
769
770 if (!problem->is_time_dependent())
771 log_and_throw_error("Operator splitting requires a transient problem.");
772
773 igl::Timer timer;
774 timer.start();
775 logger().info("Solving {}", primary_assembler_->name());
776
777 prepare_initial_solution(sol);
778
779 Eigen::MatrixXd velocity, pressure;
780 split_solution(sol, velocity, pressure);
781 stack_solution(velocity, pressure, sol);
782 save_timestep(t0, 0, t0, dt, sol);
783
784 Eigen::MatrixXd local_pts;
785 const auto &gbases = space_.geometry_basis_list();
786 assert(space_.disc_orders.size() > 0 && "Operator splitting requires initialized FE orders");
787 const int discr_order = space_.disc_orders.maxCoeff();
788 if (mesh_->dimension() == 2)
789 {
790 if (gbases[0].bases.size() == 3)
791 autogen::p_nodes_2d(discr_order, local_pts);
792 else
793 autogen::q_nodes_2d(discr_order, local_pts);
794 }
795 else
796 {
797 if (gbases[0].bases.size() == 4)
798 autogen::p_nodes_3d(discr_order, local_pts);
799 else
800 autogen::q_nodes_3d(discr_order, local_pts);
801 }
802
803 std::vector<int> bnd_nodes;
804 bnd_nodes.reserve(boundary_.boundary_nodes.size() / std::max(1, mesh_->dimension()));
805 for (auto it = boundary_.boundary_nodes.begin(); it != boundary_.boundary_nodes.end(); ++it)
806 {
807 if (!(*it % mesh_->dimension()))
808 continue;
809 bnd_nodes.push_back(*it / mesh_->dimension());
810 }
811
812 const int n_el = int(space_.basis_list().size());
813 const int shape = gbases[0].bases.size();
814
815 auto fluid_assembler = std::dynamic_pointer_cast<assembler::OperatorSplitting>(primary_assembler_);
816 if (!fluid_assembler)
817 log_and_throw_error("Invalid assembler {}!", primary_assembler_ ? primary_assembler_->name() : name());
818 const double viscosity = fluid_assembler->viscosity()(0, 0, 0, 0, 0);
819 assert(viscosity >= 0);
820
821 logger().info("Matrices assembly...");
822 StiffnessMatrix stiffness_viscosity, mixed_stiffness, velocity_mass, pressure_stiffness;
823
824 assembler::Laplacian lapl_assembler;
825 lapl_assembler.set_size(1);
826 lapl_assembler.assemble(mesh_->is_volume(), space_.n_bases, space_.basis_list(), gbases, ass_vals_cache_, 0, stiffness_viscosity);
827 mass_assembler_->set_size(1);
828 mass_assembler_->assemble(mesh_->is_volume(), space_.n_bases, space_.basis_list(), gbases, mass_ass_vals_cache_, 0, mass_, true);
829
830 lapl_assembler.assemble(mesh_->is_volume(), pressure_space_.n_bases, pressure_space_.basis_list(), gbases, pressure_ass_vals_cache_, 0, pressure_stiffness);
831
832 mixed_assembler_->assemble(mesh_->is_volume(), pressure_space_.n_bases, space_.n_bases, pressure_space_.basis_list(), space_.basis_list(), gbases,
833 pressure_ass_vals_cache_, ass_vals_cache_, 0, mixed_stiffness);
834 mass_assembler_->set_size(mesh_->dimension());
835 mass_assembler_->assemble(mesh_->is_volume(), space_.n_bases, space_.basis_list(), gbases, mass_ass_vals_cache_, 0, velocity_mass, true);
836 mixed_stiffness = mixed_stiffness.transpose();
837 logger().info("Matrices assembly ends!");
838
840 *mesh_, shape, n_el, boundary_.local_boundary, boundary_.boundary_nodes, pressure_boundary_.boundary_nodes, bnd_nodes, mass_,
841 stiffness_viscosity, pressure_stiffness, velocity_mass, dt, viscosity, args["solver"]["linear"]);
842
843 const QuadratureOrders boundary_samples = n_boundary_samples(space_.disc_orders.maxCoeff(), space_.disc_ordersq.maxCoeff(), discr_order);
844 for (int t = 1; t <= time_steps; ++t)
845 {
846 const double time = t0 + t * dt;
847 logger().info("{}/{} steps, t={}s", t, time_steps, time);
848
849 logger().info("Advection...");
850 if (args["space"]["advanced"]["use_particle_advection"])
851 solver.advection_FLIP(*mesh_, gbases, space_.basis_list(), velocity, dt, local_pts);
852 else
853 solver.advection(*mesh_, gbases, space_.basis_list(), velocity, dt, local_pts);
854 logger().info("Advection finished!");
855
856 rhs_assembler_->set_bc(
857 boundary_.local_boundary, boundary_.boundary_nodes, boundary_samples, boundary_.local_neumann_boundary, velocity, Eigen::MatrixXd(), time);
858
859 logger().info("Solving diffusion...");
860 if (viscosity > 0)
861 solver.solve_diffusion_1st(mass_, bnd_nodes, velocity);
862 logger().info("Diffusion solved!");
863
864 solver.external_force(*mesh_, *primary_assembler_, gbases, space_.basis_list(), dt, velocity, local_pts, problem, time);
865
866 logger().info("Pressure projection...");
867 solver.solve_pressure(mixed_stiffness, pressure_boundary_.boundary_nodes, velocity, pressure);
868 solver.projection(space_.n_bases, gbases, space_.basis_list(), pressure_space_.basis_list(), local_pts, pressure, velocity);
869 logger().info("Pressure projection finished!");
870
871 pressure = pressure / dt;
872
873 rhs_assembler_->set_bc(
874 boundary_.local_boundary, boundary_.boundary_nodes, boundary_samples, boundary_.local_neumann_boundary, velocity, Eigen::MatrixXd(), time);
875
876 stack_solution(velocity, pressure, sol);
877 save_timestep(time, t, t0, dt, sol);
878 save_step_state(t0, dt, t, sol, nullptr);
879 notify_time_step(t, time_steps, t0, dt);
880 }
881
882 timer.stop();
883 timings.solving_time = timer.getElapsedTime();
884 logger().info(" took {}s", timings.solving_time);
885 }
886} // namespace polyfem::varform
std::array< Matrix< int, 3, 3 >, 3 > space_
virtual void set_size(const int size)
Definition Assembler.hpp:67
static std::shared_ptr< MixedAssembler > make_mixed_assembler(const std::string &formulation)
static std::string other_assembler_name(const std::string &formulation)
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.
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 9, 1 > assemble(const LinearAssemblerData &data) const override
computes local stiffness matrix (1x1) for bases i,j where i,j is passed in through data ie integral o...
Definition Laplacian.cpp:62
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).
Eigen::MatrixXd read_matrix(const std::string &path) const
bool exists(const std::string &path) const
void write_matrix(const std::string &path, const Eigen::MatrixXd &value)
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
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
int dimension() const
utily for dimension
Definition Mesh.hpp:138
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
void solve_pressure(const StiffnessMatrix &mixed_stiffness, const std::vector< int > &pressure_boundary_nodes, Eigen::MatrixXd &sol, Eigen::MatrixXd &pressure)
void projection(const StiffnessMatrix &velocity_mass, const StiffnessMatrix &mixed_stiffness, const std::vector< int > &boundary_nodes_, Eigen::MatrixXd &sol, const Eigen::MatrixXd &pressure)
void external_force(const mesh::Mesh &mesh, const assembler::Assembler &assembler, const std::vector< basis::ElementBases > &gbases, const std::vector< basis::ElementBases > &bases, const double dt, Eigen::MatrixXd &sol, const Eigen::MatrixXd &local_pts, const std::shared_ptr< assembler::Problem > problem, const double time)
void advection(const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &gbases, const std::vector< basis::ElementBases > &bases, Eigen::MatrixXd &sol, const double dt, const Eigen::MatrixXd &local_pts, const int order=1, const int RK=1)
void advection_FLIP(const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &gbases, const std::vector< basis::ElementBases > &bases, Eigen::MatrixXd &sol, const double dt, const Eigen::MatrixXd &local_pts, const int order=1)
void solve_diffusion_1st(const StiffnessMatrix &mass, const std::vector< int > &bnd_nodes, Eigen::MatrixXd &sol)
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
std::shared_ptr< assembler::Mass > mass_assembler_
void split_solution(const Eigen::MatrixXd &stacked, Eigen::MatrixXd &primary, Eigen::MatrixXd &pressure) const
void save_json(const Eigen::MatrixXd &solution, std::ostream &out) const override
Save the solution to a JSON file, for output purposes.
void prepare_initial_solution(Eigen::MatrixXd &sol) const
void export_data(const Eigen::MatrixXd &solution) const override
void build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args) override
std::shared_ptr< assembler::MixedAssembler > mixed_assembler_
std::shared_ptr< assembler::Assembler > primary_assembler_
void assemble_rhs(const mesh::Mesh &mesh) override
std::shared_ptr< assembler::RhsAssembler > rhs_assembler_
void deserialize_checkpoint(const io::CheckpointReader &reader, Eigen::MatrixXd &solution) override
std::shared_ptr< assembler::Assembler > pressure_assembler_
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 stack_solution(const Eigen::MatrixXd &primary, const Eigen::MatrixXd &pressure, Eigen::MatrixXd &stacked) const
void serialize_checkpoint(io::CheckpointWriter &writer, const Eigen::MatrixXd &solution, const io::CheckpointMetadata &metadata) const override
io::OutputSpace output_space() const override
Get the output space of the variational formulation, for output purposes.
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.
std::string name() const override
Get the name of the variational formulation.
io::OutStatsData compute_errors(const Eigen::MatrixXd &solution) override
Get the error statistics of the variational formulation, for output purposes.
void load_mesh(const mesh::Mesh &mesh, const json &args) override
void assemble_mass_mat(const mesh::Mesh &mesh, const json &args) override
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
str func
Definition p_bases.py:417
void q_nodes_2d(const int q, Eigen::MatrixXd &val)
void p_nodes_2d(const int p, Eigen::MatrixXd &val)
void p_nodes_3d(const int p, Eigen::MatrixXd &val)
void q_nodes_3d(const int q, Eigen::MatrixXd &val)
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::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
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 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