418 const Eigen::MatrixXd &solution,
422 const std::vector<std::pair<std::string, std::shared_ptr<solver::Form>>> &named_forms,
426 std::vector<io::OutputField> fields;
433 const int actual_dim =
problem->is_scalar() ? 1 :
mesh_->dimension();
434 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
435 const bool material_params = paraview_options[
"material"];
436 const bool body_ids = paraview_options[
"body_ids"];
437 const bool velocity = paraview_options[
"velocity"];
438 const bool acceleration = paraview_options[
"acceleration"];
439 const bool forces = paraview_options[
"forces"] && !
problem->is_scalar();
440 const bool tensor_values = paraview_options[
"tensor_values"] && !
problem->is_scalar();
441 const bool scalar_values = paraview_options[
"scalar_values"];
442 const bool use_spline =
args[
"space"][
"basis_type"] ==
"Spline";
443 const bool explicit_fields = !options.
fields.empty();
445 const auto resize_to_output_rows = [&](Eigen::MatrixXd &values) {
446 if (output_rows <= values.rows())
449 const int previous_rows = values.rows();
450 values.conservativeResize(output_rows, values.cols());
451 values.bottomRows(output_rows - previous_rows).setZero();
454 const auto append_obstacle_values = [&](Eigen::MatrixXd &sampled_values,
const Eigen::MatrixXd &dof_values) ->
bool {
457 resize_to_output_rows(sampled_values);
458 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
461 const bool has_obstacle_rows =
463 && sample.
points.cols() == obstacle->
v().cols()
466 if (!has_obstacle_rows)
467 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
469 sampled_values.conservativeResize(sampled_values.rows() + obstacle->
n_vertices(), sampled_values.cols());
470 if (dof_values.rows() >= obstacle->
ndof())
471 sampled_values.bottomRows(obstacle->
n_vertices()) =
474 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
478 const auto sample_dof_field = [&](
const Eigen::MatrixXd &dof_values,
const int field_dim, Eigen::MatrixXd &values) ->
bool {
479 if (dof_values.size() <= 0 || field_dim <= 0)
482 if (has_element_samples)
490 values.row(i).setZero();
494 Eigen::MatrixXd local_sol, local_grad;
497 element_id, sample.
local_points.row(i), dof_values, local_sol, local_grad);
499 for (
int d = 0; d < field_dim; ++d)
500 values(i, d) = local_sol(d);
503 return append_obstacle_values(values, dof_values);
508 values.resize(sample.
node_ids.size(), field_dim);
509 for (
int i = 0; i < sample.
node_ids.size(); ++i)
511 const int node_id = sample.
node_ids(i);
512 for (
int d = 0; d < field_dim; ++d)
514 const int dof = node_id * field_dim + d;
515 if (dof < 0 || dof >= dof_values.rows())
517 values(i, d) = dof_values(dof);
521 return sample.
points.rows() == 0 || sample.
points.rows() == values.rows();
527 const auto append_sampled_dof_field = [&](
const std::string &
name,
const Eigen::MatrixXd &dof_values,
const int field_dim) {
528 Eigen::MatrixXd values;
529 if (sample_dof_field(dof_values, field_dim, values))
533 const auto append_scalar_values = [&]() {
534 if (!scalar_values || problem->is_scalar() || !has_element_samples)
537 const bool wants_scalar = options.fields.empty()
538 || options.export_field(
"von_mises")
539 || options.export_field(
"von_mises_avg");
543 std::vector<assembler::Assembler::NamedMatrix> point_values;
544 for (
int i = 0; i < sample.local_points.rows(); ++i)
546 const int element_id = sample.element_ids(i);
550 std::vector<assembler::Assembler::NamedMatrix> local_values;
551 primary_assembler_->compute_scalar_value(
555 if (point_values.empty())
557 point_values.resize(local_values.size());
558 for (
int k = 0; k < local_values.size(); ++k)
560 point_values[k].first = local_values[k].first;
561 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
565 for (
int k = 0; k < local_values.size(); ++k)
566 point_values[k].second.row(i) = local_values[k].second;
569 for (
const auto &[name, values] : point_values)
571 if (options.export_field(name))
576 const auto append_tensor_values = [&]() {
577 if (!tensor_values || problem->is_scalar() || !has_element_samples)
580 const bool wants_tensor = options.fields.empty()
581 || options.export_field(
"cauchy_stess")
582 || options.export_field(
"pk1_stess")
583 || options.export_field(
"pk2_stess")
584 || options.export_field(
"F");
588 std::vector<assembler::Assembler::NamedMatrix> point_values;
589 for (
int i = 0; i < sample.local_points.rows(); ++i)
591 const int element_id = sample.element_ids(i);
595 std::vector<assembler::Assembler::NamedMatrix> local_values;
596 primary_assembler_->compute_tensor_value(
597 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
600 if (point_values.empty())
602 point_values.resize(local_values.size());
603 for (
int k = 0; k < local_values.size(); ++k)
605 point_values[k].first = local_values[k].first;
606 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
610 for (
int k = 0; k < local_values.size(); ++k)
611 point_values[k].second.row(i) = local_values[k].second;
614 for (
const auto &[name, values] : point_values)
616 if (!options.export_field(name))
619 const int stride = mesh_->dimension();
620 assert(values.cols() % stride == 0);
621 for (
int i = 0; i < values.cols(); i += stride)
623 const int ii = (i / stride) + 1;
629 const auto append_averaged_values = [&]() {
630 if (use_spline || problem->is_scalar() || !has_element_samples || (!scalar_values && !tensor_values))
633 const bool wants_avg = options.fields.empty()
634 || options.export_field(
"von_mises_avg")
635 || options.export_field(
"cauchy_stess_avg")
636 || options.export_field(
"pk1_stess_avg")
637 || options.export_field(
"pk2_stess_avg")
638 || options.export_field(
"F_avg");
642 Eigen::MatrixXd areas(
space_.n_bases, 1);
644 std::vector<assembler::Assembler::NamedMatrix> tmp_s, tmp_t;
645 std::vector<Eigen::MatrixXd> avg_scalar, avg_tensor;
647 for (
int e = 0;
e < int(
space_.basis_list().size()); ++
e)
649 Eigen::MatrixXd local_pts;
650 if (mesh_->is_simplex(e))
652 if (mesh_->dimension() == 3)
657 else if (mesh_->is_cube(e))
659 if (mesh_->dimension() == 3)
664 else if (mesh_->is_prism(e))
673 const basis::ElementBases &bs =
space_.basis_list()[
e];
674 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
676 assembler::ElementAssemblyValues
vals;
681 primary_assembler_->compute_scalar_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_s);
683 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_t);
685 if (avg_scalar.empty() && !tmp_s.empty())
687 avg_scalar.resize(tmp_s.size());
688 for (
auto &m : avg_scalar)
689 m.setZero(
space_.n_bases, 1);
691 if (avg_tensor.empty() && !tmp_t.empty())
693 avg_tensor.resize(tmp_t.size());
694 for (
auto &m : avg_tensor)
695 m.setZero(
space_.n_bases, actual_dim * actual_dim);
698 for (
size_t j = 0; j < bs.bases.size(); ++j)
700 const basis::Basis &
b = bs.bases[j];
701 if (
b.global().size() > 1)
704 const int index =
b.global().front().index;
705 areas(index) += area;
706 for (
int k = 0; k < tmp_s.size(); ++k)
707 avg_scalar[k](index) += tmp_s[k].second(j) * area;
708 for (
int k = 0; k < tmp_t.size(); ++k)
709 avg_tensor[k].row(index) += tmp_t[k].second.row(j) * area;
713 for (
auto &m : avg_scalar)
714 for (int i = 0; i < m.rows(); ++i)
717 for (
auto &m : avg_tensor)
718 for (int i = 0; i < m.rows(); ++i)
720 m.row(i) /= areas(i);
722 for (
int k = 0; k < tmp_s.size(); ++k)
724 const std::string name = fmt::format(
"{:s}_avg", tmp_s[k].first);
725 if (!options.export_field(name))
728 Eigen::MatrixXd sampled;
729 if (sample_dof_field(avg_scalar[k], 1, sampled))
733 for (
int k = 0; k < tmp_t.size(); ++k)
735 const std::string base_name = fmt::format(
"{:s}_avg", tmp_t[k].first);
736 if (!options.export_field(base_name))
739 Eigen::MatrixXd sampled;
740 if (!sample_dof_field(
utils::flatten(avg_tensor[k]), actual_dim * actual_dim, sampled))
743 const int stride = mesh_->dimension();
744 for (
int i = 0; i < sampled.cols(); i += stride)
746 const int ii = (i / stride) + 1;
752 const auto append_material_fields = [&]() {
753 if (!material_params || !has_element_samples)
756 const auto ¶ms = primary_assembler_->parameters();
757 std::map<std::string, Eigen::MatrixXd> param_values;
758 for (
const auto &[p, _] : params)
759 param_values[p].setZero(output_rows, 1);
760 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
762 const auto &density = mass_assembler_->density();
763 for (
int i = 0; i < sample.local_points.rows(); ++i)
765 const int element_id = sample.element_ids(i);
769 for (
const auto &[p, func] : params)
770 param_values.at(p)(i) =
func(sample.local_points.row(i), sample.
points.row(i), sample.time, element_id);
771 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
774 for (
const auto &[name, values] : param_values)
775 if (options.export_field(name))
777 if (options.export_field(
"rho"))
781 const auto append_body_ids = [&]() {
782 if (!body_ids || !options.export_field(
"body_ids") || !has_element_samples)
785 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
786 for (
int i = 0; i < sample.element_ids.size(); ++i)
788 const int element_id = sample.element_ids(i);
790 ids(i) = mesh_->get_body_id(element_id);
795 const auto compute_traction_forces = [&]() {
796 Eigen::MatrixXd traction_forces;
797 traction_forces.setZero(
space_.n_bases * actual_dim, 1);
799 Eigen::MatrixXd uv,
points, normals;
800 Eigen::VectorXd weights;
801 Eigen::VectorXi global_primitive_ids;
802 assembler::ElementAssemblyValues
vals;
804 for (
const auto &lb : boundary_.total_local_boundary)
806 const int e = lb.element_id();
808 lb, elastic_boundary_samples(), *mesh_,
false, uv, points, normals, weights, global_primitive_ids);
812 const basis::ElementBases &bs =
space_.basis_list()[
e];
813 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
814 vals.
compute(e, mesh_->is_volume(), points, bs, gbs);
816 for (
int n = 0; n < normals.rows(); ++n)
818 Eigen::MatrixXd deform_mat = Eigen::MatrixXd::Zero(actual_dim, actual_dim);
819 for (
const auto &b :
vals.basis_values)
821 for (
const auto &g :
b.global)
823 for (
int d = 0; d < actual_dim; ++d)
824 deform_mat.row(d) += solution(
g.index * actual_dim + d) *
b.grad.row(n);
828 Eigen::MatrixXd trafo =
vals.
jac_it[n].inverse() + deform_mat;
829 normals.row(n) = normals.row(n) * trafo.inverse();
830 normals.row(n).normalize();
833 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
834 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, points, solution), tensor_flat);
839 const int g_index = v.global[0].index * actual_dim;
840 for (
int q = 0; q <
points.rows(); ++q)
842 assert(tensor_flat[0].first ==
"cauchy_stess");
843 Eigen::MatrixXd stress_tensor =
utils::unflatten(tensor_flat[0].second.row(q), actual_dim);
844 traction_forces.block(g_index, 0, actual_dim, 1) += stress_tensor * normals.row(q).transpose() * v.val(q) * weights(q);
849 return traction_forces;
852 const auto append_traction_force = [&]() {
853 if (problem->is_scalar() || !explicit_fields || !options.export_field(
"traction_force"))
856 if (has_element_samples && sample.normals.rows() == sample.local_points.rows() && sample.primitive_ids.size() == sample.local_points.rows())
858 const Eigen::MatrixXd displaced_normals = displaced_output_normals(sample, solution);
859 const Eigen::MatrixXd &normals = displaced_normals.rows() == sample.normals.rows() ? displaced_normals : sample.normals;
860 Eigen::MatrixXd values = Eigen::MatrixXd::Zero(output_rows, actual_dim);
861 for (
int i = 0; i < sample.local_points.rows(); ++i)
863 const int element_id = sample.element_ids(i);
867 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
868 primary_assembler_->compute_tensor_value(
869 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
872 assert(tensor_flat[0].first ==
"cauchy_stess");
873 Eigen::Map<Eigen::MatrixXd> tensor(tensor_flat[0].second.data(), actual_dim, actual_dim);
874 values.row(i) = normals.row(i) * tensor;
877 const int primitive_id = sample.primitive_ids(i);
878 if (mesh_->is_volume())
880 if (mesh_->is_simplex(element_id))
881 area = mesh_->tri_area(primitive_id);
882 else if (mesh_->is_cube(element_id))
883 area = mesh_->quad_area(primitive_id);
884 else if (mesh_->is_prism(element_id))
885 area = mesh_->n_face_vertices(primitive_id) == 4 ? mesh_->quad_area(primitive_id) : mesh_->tri_area(primitive_id);
889 area = mesh_->edge_length(primitive_id);
891 values.row(i) *= area;
897 append_sampled_dof_field(
"traction_force", compute_traction_forces(), actual_dim);
900 append_scalar_values();
901 append_tensor_values();
902 append_averaged_values();
903 append_material_fields();
906 if ((paraview_options[
"jacobian_validity"] || (explicit_fields && options.export_field(
"validity")))
907 && has_element_samples
908 && sample.primitive_ids.size() == 0)
911 mesh_->dimension(),
space_.basis_list(),
space_.geometry_basis_list(), solution);
912 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
913 for (
int i = 0; i < sample.element_ids.size(); ++i)
915 validity(i) = std::find(
916 invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i))
917 != invalid_elements.end();
922 if (problem->is_time_dependent())
924 if (velocity && options.export_field(
"velocity"))
925 append_sampled_dof_field(
927 time_integrator ? time_integrator->v_prev() : Eigen::VectorXd::Zero(solution.size()),
929 if (acceleration && options.export_field(
"acceleration"))
930 append_sampled_dof_field(
932 time_integrator ? time_integrator->a_prev() : Eigen::VectorXd::Zero(solution.size()),
938 const double s = time_integrator ? time_integrator->acceleration_scaling() : 1;
939 for (
const auto &[name, form] : named_forms)
941 const std::string field_name = name +
"_forces";
942 if (!options.export_field(field_name))
945 Eigen::VectorXd force;
946 if (form && form->enabled())
948 form->first_derivative(solution, force);
953 force.setZero(solution.size());
955 append_sampled_dof_field(field_name, force, actual_dim);
959 append_traction_force();
961 if (explicit_fields && options.export_field(
"gradient_of_elastic_potential") && elastic_form)
963 Eigen::VectorXd potential_grad;
964 elastic_form->first_derivative(solution, potential_grad);
965 append_sampled_dof_field(
"gradient_of_elastic_potential", potential_grad, actual_dim);
968 if (explicit_fields && options.export_field(
"gradient_of_contact_potential") && contact_form && contact_form->weight() > 0)
970 Eigen::VectorXd potential_grad;
971 contact_form->first_derivative(solution, potential_grad);
972 potential_grad *= -contact_form->barrier_stiffness() / contact_form->weight();
973 append_sampled_dof_field(
"gradient_of_contact_potential", potential_grad, actual_dim);
976 append_primary_output_fields(fields, sample, solution, options, obstacle);