455 const Eigen::MatrixXd &solution,
459 const std::vector<std::pair<std::string, std::shared_ptr<solver::Form>>> &named_forms,
463 std::vector<io::OutputField> fields;
470 const int actual_dim =
problem->is_scalar() ? 1 :
mesh_->dimension();
471 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
472 const bool material_params = paraview_options[
"material"];
473 const bool body_ids = paraview_options[
"body_ids"];
474 const bool velocity = paraview_options[
"velocity"];
475 const bool acceleration = paraview_options[
"acceleration"];
476 const bool forces = paraview_options[
"forces"] && !
problem->is_scalar();
477 const bool tensor_values = paraview_options[
"tensor_values"] && !
problem->is_scalar();
478 const bool scalar_values = paraview_options[
"scalar_values"];
479 const bool use_spline =
args[
"space"][
"basis_type"] ==
"Spline";
480 const bool explicit_fields = !options.
fields.empty();
482 const auto resize_to_output_rows = [&](Eigen::MatrixXd &values) {
483 if (output_rows <= values.rows())
486 const int previous_rows = values.rows();
487 values.conservativeResize(output_rows, values.cols());
488 values.bottomRows(output_rows - previous_rows).setZero();
491 const auto append_obstacle_values = [&](Eigen::MatrixXd &sampled_values,
const Eigen::MatrixXd &dof_values) ->
bool {
494 resize_to_output_rows(sampled_values);
495 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
498 const bool has_obstacle_rows =
500 && sample.
points.cols() == obstacle->
v().cols()
503 if (!has_obstacle_rows)
504 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
506 sampled_values.conservativeResize(sampled_values.rows() + obstacle->
n_vertices(), sampled_values.cols());
507 if (dof_values.rows() >= obstacle->
ndof())
509 const Eigen::MatrixXd obstacle_values =
511 if (obstacle_values.rows() == obstacle->
n_vertices()
512 && obstacle_values.cols() == sampled_values.cols())
513 sampled_values.bottomRows(obstacle->
n_vertices()) = obstacle_values;
515 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
518 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
522 const auto sample_dof_field = [&](
const Eigen::MatrixXd &dof_values,
const int field_dim, Eigen::MatrixXd &values) ->
bool {
523 if (dof_values.size() <= 0 || field_dim <= 0)
526 if (has_element_samples)
534 values.row(i).setZero();
538 Eigen::MatrixXd local_sol, local_grad;
541 element_id, sample.
local_points.row(i), dof_values, local_sol, local_grad);
543 for (
int d = 0; d < field_dim; ++d)
544 values(i, d) = local_sol(d);
547 return append_obstacle_values(values, dof_values);
552 values.resize(sample.
node_ids.size(), field_dim);
553 for (
int i = 0; i < sample.
node_ids.size(); ++i)
555 const int node_id = sample.
node_ids(i);
556 for (
int d = 0; d < field_dim; ++d)
558 const int dof = node_id * field_dim + d;
559 if (dof < 0 || dof >= dof_values.rows())
561 values(i, d) = dof_values(dof);
565 return sample.
points.rows() == 0 || sample.
points.rows() == values.rows();
571 const auto append_sampled_dof_field = [&](
const std::string &
name,
const Eigen::MatrixXd &dof_values,
const int field_dim) {
572 Eigen::MatrixXd values;
573 if (sample_dof_field(dof_values, field_dim, values))
577 const auto append_scalar_values = [&]() {
578 if (!scalar_values || problem->is_scalar() || !has_element_samples)
581 const bool wants_scalar = options.fields.empty()
582 || options.export_field(
"von_mises")
583 || options.export_field(
"von_mises_avg");
587 std::vector<assembler::Assembler::NamedMatrix> point_values;
588 for (
int i = 0; i < sample.local_points.rows(); ++i)
590 const int element_id = sample.element_ids(i);
594 std::vector<assembler::Assembler::NamedMatrix> local_values;
595 primary_assembler_->compute_scalar_value(
599 if (point_values.empty())
601 point_values.resize(local_values.size());
602 for (
int k = 0; k < local_values.size(); ++k)
604 point_values[k].first = local_values[k].first;
605 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
609 for (
int k = 0; k < local_values.size(); ++k)
610 point_values[k].second.row(i) = local_values[k].second;
613 for (
const auto &[name, values] : point_values)
615 if (options.export_field(name))
620 const auto append_tensor_values = [&]() {
621 if (!tensor_values || problem->is_scalar() || !has_element_samples)
624 const bool wants_tensor = options.fields.empty()
625 || options.export_field(
"cauchy_stess")
626 || options.export_field(
"pk1_stess")
627 || options.export_field(
"pk2_stess")
628 || options.export_field(
"F");
632 std::vector<assembler::Assembler::NamedMatrix> point_values;
633 for (
int i = 0; i < sample.local_points.rows(); ++i)
635 const int element_id = sample.element_ids(i);
639 std::vector<assembler::Assembler::NamedMatrix> local_values;
640 primary_assembler_->compute_tensor_value(
641 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
644 if (point_values.empty())
646 point_values.resize(local_values.size());
647 for (
int k = 0; k < local_values.size(); ++k)
649 point_values[k].first = local_values[k].first;
650 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
654 for (
int k = 0; k < local_values.size(); ++k)
655 point_values[k].second.row(i) = local_values[k].second;
658 for (
const auto &[name, values] : point_values)
660 if (!options.export_field(name))
663 const int stride = mesh_->dimension();
664 assert(values.cols() % stride == 0);
665 for (
int i = 0; i < values.cols(); i += stride)
667 const int ii = (i / stride) + 1;
673 const auto append_averaged_values = [&]() {
674 if (use_spline || problem->is_scalar() || !has_element_samples || (!scalar_values && !tensor_values))
677 const bool wants_avg = options.fields.empty()
678 || options.export_field(
"von_mises_avg")
679 || options.export_field(
"cauchy_stess_avg")
680 || options.export_field(
"pk1_stess_avg")
681 || options.export_field(
"pk2_stess_avg")
682 || options.export_field(
"F_avg");
686 Eigen::MatrixXd areas(
space_.n_bases, 1);
688 std::vector<assembler::Assembler::NamedMatrix> tmp_s, tmp_t;
689 std::vector<Eigen::MatrixXd> avg_scalar, avg_tensor;
691 for (
int e = 0;
e < int(
space_.basis_list().size()); ++
e)
693 Eigen::MatrixXd local_pts;
694 if (mesh_->is_simplex(e))
696 if (mesh_->dimension() == 3)
701 else if (mesh_->is_cube(e))
703 if (mesh_->dimension() == 3)
708 else if (mesh_->is_prism(e))
717 const basis::ElementBases &bs =
space_.basis_list()[
e];
718 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
720 assembler::ElementAssemblyValues
vals;
725 primary_assembler_->compute_scalar_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_s);
727 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_t);
729 if (avg_scalar.empty() && !tmp_s.empty())
731 avg_scalar.resize(tmp_s.size());
732 for (
auto &m : avg_scalar)
733 m.setZero(
space_.n_bases, 1);
735 if (avg_tensor.empty() && !tmp_t.empty())
737 avg_tensor.resize(tmp_t.size());
738 for (
auto &m : avg_tensor)
739 m.setZero(
space_.n_bases, actual_dim * actual_dim);
742 for (
size_t j = 0; j < bs.bases.size(); ++j)
744 const basis::Basis &
b = bs.bases[j];
745 if (
b.global().size() > 1)
748 const int index =
b.global().front().index;
749 areas(index) += area;
750 for (
int k = 0; k < tmp_s.size(); ++k)
751 avg_scalar[k](index) += tmp_s[k].second(j) * area;
752 for (
int k = 0; k < tmp_t.size(); ++k)
753 avg_tensor[k].row(index) += tmp_t[k].second.row(j) * area;
757 for (
auto &m : avg_scalar)
758 for (int i = 0; i < m.rows(); ++i)
761 for (
auto &m : avg_tensor)
762 for (int i = 0; i < m.rows(); ++i)
764 m.row(i) /= areas(i);
766 for (
int k = 0; k < tmp_s.size(); ++k)
768 const std::string name = fmt::format(
"{:s}_avg", tmp_s[k].first);
769 if (!options.export_field(name))
772 Eigen::MatrixXd sampled;
773 if (sample_dof_field(avg_scalar[k], 1, sampled))
777 for (
int k = 0; k < tmp_t.size(); ++k)
779 const std::string base_name = fmt::format(
"{:s}_avg", tmp_t[k].first);
780 if (!options.export_field(base_name))
783 Eigen::MatrixXd sampled;
784 if (!sample_dof_field(
utils::flatten(avg_tensor[k]), actual_dim * actual_dim, sampled))
787 const int stride = mesh_->dimension();
788 for (
int i = 0; i < sampled.cols(); i += stride)
790 const int ii = (i / stride) + 1;
796 const auto append_material_fields = [&]() {
797 if (!material_params || !has_element_samples)
800 const auto ¶ms = primary_assembler_->parameters();
801 std::map<std::string, Eigen::MatrixXd> param_values;
802 for (
const auto &[p, _] : params)
803 param_values[p].setZero(output_rows, 1);
804 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
806 const auto &density = mass_assembler_->density();
807 for (
int i = 0; i < sample.local_points.rows(); ++i)
809 const int element_id = sample.element_ids(i);
813 for (
const auto &[p, func] : params)
814 param_values.at(p)(i) =
func(sample.local_points.row(i), sample.
points.row(i), sample.time, element_id);
815 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
818 for (
const auto &[name, values] : param_values)
819 if (options.export_field(name))
821 if (options.export_field(
"rho"))
825 const auto append_body_ids = [&]() {
826 if (!body_ids || !options.export_field(
"body_ids") || !has_element_samples)
829 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
830 for (
int i = 0; i < sample.element_ids.size(); ++i)
832 const int element_id = sample.element_ids(i);
834 ids(i) = mesh_->get_body_id(element_id);
839 const auto compute_traction_forces = [&]() {
840 Eigen::MatrixXd traction_forces;
841 traction_forces.setZero(
space_.n_bases * actual_dim, 1);
843 Eigen::MatrixXd uv,
points, normals;
845 Eigen::VectorXi global_primitive_ids;
846 assembler::ElementAssemblyValues
vals;
848 for (
const auto &lb : boundary_.total_local_boundary)
850 const int e = lb.element_id();
852 lb, elastic_boundary_samples(), *mesh_,
false, uv, points, normals,
weights, global_primitive_ids);
856 const basis::ElementBases &bs =
space_.basis_list()[
e];
857 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
858 vals.
compute(e, mesh_->is_volume(), points, bs, gbs);
860 for (
int n = 0; n < normals.rows(); ++n)
862 Eigen::MatrixXd deform_mat = Eigen::MatrixXd::Zero(actual_dim, actual_dim);
863 for (
const auto &b :
vals.basis_values)
865 for (
const auto &g :
b.global)
867 for (
int d = 0; d < actual_dim; ++d)
868 deform_mat.row(d) += solution(
g.index * actual_dim + d) *
b.grad.row(n);
872 Eigen::MatrixXd trafo =
vals.
jac_it[n].inverse() + deform_mat;
873 normals.row(n) = normals.row(n) * trafo.inverse();
874 normals.row(n).normalize();
877 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
878 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, points, solution), tensor_flat);
883 const int g_index = v.global[0].index * actual_dim;
884 for (
int q = 0; q <
points.rows(); ++q)
886 assert(tensor_flat[0].first ==
"cauchy_stess");
887 Eigen::MatrixXd stress_tensor =
utils::unflatten(tensor_flat[0].second.row(q), actual_dim);
888 traction_forces.block(g_index, 0, actual_dim, 1) += stress_tensor * normals.row(q).transpose() * v.val(q) *
weights(q);
893 return traction_forces;
896 const auto append_traction_force = [&]() {
897 if (problem->is_scalar() || !explicit_fields || !options.export_field(
"traction_force"))
900 if (has_element_samples && sample.normals.rows() == sample.local_points.rows() && sample.primitive_ids.size() == sample.local_points.rows())
902 const Eigen::MatrixXd displaced_normals = displaced_output_normals(sample, solution);
903 const Eigen::MatrixXd &normals = displaced_normals.rows() == sample.normals.rows() ? displaced_normals : sample.normals;
904 Eigen::MatrixXd values = Eigen::MatrixXd::Zero(output_rows, actual_dim);
905 for (
int i = 0; i < sample.local_points.rows(); ++i)
907 const int element_id = sample.element_ids(i);
911 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
912 primary_assembler_->compute_tensor_value(
913 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
916 assert(tensor_flat[0].first ==
"cauchy_stess");
917 Eigen::Map<Eigen::MatrixXd> tensor(tensor_flat[0].second.data(), actual_dim, actual_dim);
918 values.row(i) = normals.row(i) * tensor;
921 const int primitive_id = sample.primitive_ids(i);
922 if (mesh_->is_volume())
924 if (mesh_->is_simplex(element_id))
925 area = mesh_->tri_area(primitive_id);
926 else if (mesh_->is_cube(element_id))
927 area = mesh_->quad_area(primitive_id);
928 else if (mesh_->is_prism(element_id))
929 area = mesh_->n_face_vertices(primitive_id) == 4 ? mesh_->quad_area(primitive_id) : mesh_->tri_area(primitive_id);
933 area = mesh_->edge_length(primitive_id);
935 values.row(i) *= area;
941 append_sampled_dof_field(
"traction_force", compute_traction_forces(), actual_dim);
944 append_scalar_values();
945 append_tensor_values();
946 append_averaged_values();
947 append_material_fields();
950 if ((paraview_options[
"jacobian_validity"] || (explicit_fields && options.export_field(
"validity")))
951 && has_element_samples
952 && sample.primitive_ids.size() == 0)
955 mesh_->dimension(),
space_.basis_list(),
space_.geometry_basis_list(), solution);
956 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
957 for (
int i = 0; i < sample.element_ids.size(); ++i)
959 validity(i) = std::find(
960 invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i))
961 != invalid_elements.end();
966 if (problem->is_time_dependent())
968 if (velocity && options.export_field(
"velocity"))
969 append_sampled_dof_field(
971 time_integrator ? time_integrator->v_prev() : Eigen::VectorXd::Zero(solution.size()),
973 if (acceleration && options.export_field(
"acceleration"))
974 append_sampled_dof_field(
976 time_integrator ? time_integrator->a_prev() : Eigen::VectorXd::Zero(solution.size()),
982 const double s = time_integrator ? time_integrator->acceleration_scaling() : 1;
983 for (
const auto &[name, form] : named_forms)
985 const std::string field_name = name +
"_forces";
986 if (!options.export_field(field_name))
989 Eigen::VectorXd force;
990 if (form && form->enabled())
992 form->first_derivative(solution, force);
997 force.setZero(solution.size());
999 append_sampled_dof_field(field_name, force, actual_dim);
1003 append_traction_force();
1005 if (explicit_fields && options.export_field(
"gradient_of_elastic_potential") && elastic_form)
1007 Eigen::VectorXd potential_grad;
1008 elastic_form->first_derivative(solution, potential_grad);
1009 append_sampled_dof_field(
"gradient_of_elastic_potential", potential_grad, actual_dim);
1012 if (explicit_fields && options.export_field(
"gradient_of_contact_potential") && contact_form && contact_form->weight() > 0)
1014 Eigen::VectorXd potential_grad;
1015 contact_form->first_derivative(solution, potential_grad);
1016 potential_grad *= -contact_form->barrier_stiffness() / contact_form->weight();
1017 append_sampled_dof_field(
"gradient_of_contact_potential", potential_grad, actual_dim);
1020 append_primary_output_fields(fields, sample, solution, options, obstacle);