449 const Eigen::MatrixXd &solution,
453 const std::vector<std::pair<std::string, std::shared_ptr<solver::Form>>> &named_forms,
457 std::vector<io::OutputField> fields;
464 const int actual_dim =
problem->is_scalar() ? 1 :
mesh_->dimension();
465 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
466 const bool material_params = paraview_options[
"material"];
467 const bool body_ids = paraview_options[
"body_ids"];
468 const bool velocity = paraview_options[
"velocity"];
469 const bool acceleration = paraview_options[
"acceleration"];
470 const bool forces = paraview_options[
"forces"] && !
problem->is_scalar();
471 const bool tensor_values = paraview_options[
"tensor_values"] && !
problem->is_scalar();
472 const bool scalar_values = paraview_options[
"scalar_values"];
473 const bool use_spline =
args[
"space"][
"basis_type"] ==
"Spline";
474 const bool explicit_fields = !options.
fields.empty();
476 const auto resize_to_output_rows = [&](Eigen::MatrixXd &values) {
477 if (output_rows <= values.rows())
480 const int previous_rows = values.rows();
481 values.conservativeResize(output_rows, values.cols());
482 values.bottomRows(output_rows - previous_rows).setZero();
485 const auto append_obstacle_values = [&](Eigen::MatrixXd &sampled_values,
const Eigen::MatrixXd &dof_values) ->
bool {
488 resize_to_output_rows(sampled_values);
489 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
492 const bool has_obstacle_rows =
494 && sample.
points.cols() == obstacle->
v().cols()
497 if (!has_obstacle_rows)
498 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
500 sampled_values.conservativeResize(sampled_values.rows() + obstacle->
n_vertices(), sampled_values.cols());
501 if (dof_values.rows() >= obstacle->
ndof())
503 const Eigen::MatrixXd obstacle_values =
505 if (obstacle_values.rows() == obstacle->
n_vertices()
506 && obstacle_values.cols() == sampled_values.cols())
507 sampled_values.bottomRows(obstacle->
n_vertices()) = obstacle_values;
509 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
512 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
516 const auto sample_dof_field = [&](
const Eigen::MatrixXd &dof_values,
const int field_dim, Eigen::MatrixXd &values) ->
bool {
517 if (dof_values.size() <= 0 || field_dim <= 0)
520 if (has_element_samples)
528 values.row(i).setZero();
532 Eigen::MatrixXd local_sol, local_grad;
535 element_id, sample.
local_points.row(i), dof_values, local_sol, local_grad);
537 for (
int d = 0; d < field_dim; ++d)
538 values(i, d) = local_sol(d);
541 return append_obstacle_values(values, dof_values);
546 values.resize(sample.
node_ids.size(), field_dim);
547 for (
int i = 0; i < sample.
node_ids.size(); ++i)
549 const int node_id = sample.
node_ids(i);
550 for (
int d = 0; d < field_dim; ++d)
552 const int dof = node_id * field_dim + d;
553 if (dof < 0 || dof >= dof_values.rows())
555 values(i, d) = dof_values(dof);
559 return sample.
points.rows() == 0 || sample.
points.rows() == values.rows();
565 const auto append_sampled_dof_field = [&](
const std::string &
name,
const Eigen::MatrixXd &dof_values,
const int field_dim) {
566 Eigen::MatrixXd values;
567 if (sample_dof_field(dof_values, field_dim, values))
571 const auto append_scalar_values = [&]() {
572 if (!scalar_values || problem->is_scalar() || !has_element_samples)
575 const bool wants_scalar = options.fields.empty()
576 || options.export_field(
"von_mises")
577 || options.export_field(
"von_mises_avg");
581 std::vector<assembler::Assembler::NamedMatrix> point_values;
582 for (
int i = 0; i < sample.local_points.rows(); ++i)
584 const int element_id = sample.element_ids(i);
588 std::vector<assembler::Assembler::NamedMatrix> local_values;
589 primary_assembler_->compute_scalar_value(
593 if (point_values.empty())
595 point_values.resize(local_values.size());
596 for (
int k = 0; k < local_values.size(); ++k)
598 point_values[k].first = local_values[k].first;
599 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
603 for (
int k = 0; k < local_values.size(); ++k)
604 point_values[k].second.row(i) = local_values[k].second;
607 for (
const auto &[name, values] : point_values)
609 if (options.export_field(name))
614 const auto append_tensor_values = [&]() {
615 if (!tensor_values || problem->is_scalar() || !has_element_samples)
618 const bool wants_tensor = options.fields.empty()
619 || options.export_field(
"cauchy_stess")
620 || options.export_field(
"pk1_stess")
621 || options.export_field(
"pk2_stess")
622 || options.export_field(
"F");
626 std::vector<assembler::Assembler::NamedMatrix> point_values;
627 for (
int i = 0; i < sample.local_points.rows(); ++i)
629 const int element_id = sample.element_ids(i);
633 std::vector<assembler::Assembler::NamedMatrix> local_values;
634 primary_assembler_->compute_tensor_value(
635 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
638 if (point_values.empty())
640 point_values.resize(local_values.size());
641 for (
int k = 0; k < local_values.size(); ++k)
643 point_values[k].first = local_values[k].first;
644 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
648 for (
int k = 0; k < local_values.size(); ++k)
649 point_values[k].second.row(i) = local_values[k].second;
652 for (
const auto &[name, values] : point_values)
654 if (!options.export_field(name))
657 const int stride = mesh_->dimension();
658 assert(values.cols() % stride == 0);
659 for (
int i = 0; i < values.cols(); i += stride)
661 const int ii = (i / stride) + 1;
667 const auto append_averaged_values = [&]() {
668 if (use_spline || problem->is_scalar() || !has_element_samples || (!scalar_values && !tensor_values))
671 const bool wants_avg = options.fields.empty()
672 || options.export_field(
"von_mises_avg")
673 || options.export_field(
"cauchy_stess_avg")
674 || options.export_field(
"pk1_stess_avg")
675 || options.export_field(
"pk2_stess_avg")
676 || options.export_field(
"F_avg");
680 Eigen::MatrixXd areas(
space_.n_bases, 1);
682 std::vector<assembler::Assembler::NamedMatrix> tmp_s, tmp_t;
683 std::vector<Eigen::MatrixXd> avg_scalar, avg_tensor;
685 for (
int e = 0;
e < int(
space_.basis_list().size()); ++
e)
687 Eigen::MatrixXd local_pts;
688 if (mesh_->is_simplex(e))
690 if (mesh_->dimension() == 3)
695 else if (mesh_->is_cube(e))
697 if (mesh_->dimension() == 3)
702 else if (mesh_->is_prism(e))
711 const basis::ElementBases &bs =
space_.basis_list()[
e];
712 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
714 assembler::ElementAssemblyValues
vals;
719 primary_assembler_->compute_scalar_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_s);
721 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_t);
723 if (avg_scalar.empty() && !tmp_s.empty())
725 avg_scalar.resize(tmp_s.size());
726 for (
auto &m : avg_scalar)
727 m.setZero(
space_.n_bases, 1);
729 if (avg_tensor.empty() && !tmp_t.empty())
731 avg_tensor.resize(tmp_t.size());
732 for (
auto &m : avg_tensor)
733 m.setZero(
space_.n_bases, actual_dim * actual_dim);
736 for (
size_t j = 0; j < bs.bases.size(); ++j)
738 const basis::Basis &
b = bs.bases[j];
739 if (
b.global().size() > 1)
742 const int index =
b.global().front().index;
743 areas(index) += area;
744 for (
int k = 0; k < tmp_s.size(); ++k)
745 avg_scalar[k](index) += tmp_s[k].second(j) * area;
746 for (
int k = 0; k < tmp_t.size(); ++k)
747 avg_tensor[k].row(index) += tmp_t[k].second.row(j) * area;
751 for (
auto &m : avg_scalar)
752 for (int i = 0; i < m.rows(); ++i)
755 for (
auto &m : avg_tensor)
756 for (int i = 0; i < m.rows(); ++i)
758 m.row(i) /= areas(i);
760 for (
int k = 0; k < tmp_s.size(); ++k)
762 const std::string name = fmt::format(
"{:s}_avg", tmp_s[k].first);
763 if (!options.export_field(name))
766 Eigen::MatrixXd sampled;
767 if (sample_dof_field(avg_scalar[k], 1, sampled))
771 for (
int k = 0; k < tmp_t.size(); ++k)
773 const std::string base_name = fmt::format(
"{:s}_avg", tmp_t[k].first);
774 if (!options.export_field(base_name))
777 Eigen::MatrixXd sampled;
778 if (!sample_dof_field(
utils::flatten(avg_tensor[k]), actual_dim * actual_dim, sampled))
781 const int stride = mesh_->dimension();
782 for (
int i = 0; i < sampled.cols(); i += stride)
784 const int ii = (i / stride) + 1;
790 const auto append_material_fields = [&]() {
791 if (!material_params || !has_element_samples)
794 const auto ¶ms = primary_assembler_->parameters();
795 std::map<std::string, Eigen::MatrixXd> param_values;
796 for (
const auto &[p, _] : params)
797 param_values[p].setZero(output_rows, 1);
798 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
800 const auto &density = mass_assembler_->density();
801 for (
int i = 0; i < sample.local_points.rows(); ++i)
803 const int element_id = sample.element_ids(i);
807 for (
const auto &[p, func] : params)
808 param_values.at(p)(i) =
func(sample.local_points.row(i), sample.
points.row(i), sample.time, element_id);
809 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
812 for (
const auto &[name, values] : param_values)
813 if (options.export_field(name))
815 if (options.export_field(
"rho"))
819 const auto append_body_ids = [&]() {
820 if (!body_ids || !options.export_field(
"body_ids") || !has_element_samples)
823 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
824 for (
int i = 0; i < sample.element_ids.size(); ++i)
826 const int element_id = sample.element_ids(i);
828 ids(i) = mesh_->get_body_id(element_id);
833 const auto compute_traction_forces = [&]() {
834 Eigen::MatrixXd traction_forces;
835 traction_forces.setZero(
space_.n_bases * actual_dim, 1);
837 Eigen::MatrixXd uv,
points, normals;
839 Eigen::VectorXi global_primitive_ids;
840 assembler::ElementAssemblyValues
vals;
842 for (
const auto &lb : boundary_.total_local_boundary)
844 const int e = lb.element_id();
846 lb, elastic_boundary_samples(), *mesh_,
false, uv, points, normals,
weights, global_primitive_ids);
850 const basis::ElementBases &bs =
space_.basis_list()[
e];
851 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
852 vals.
compute(e, mesh_->is_volume(), points, bs, gbs);
854 for (
int n = 0; n < normals.rows(); ++n)
856 Eigen::MatrixXd deform_mat = Eigen::MatrixXd::Zero(actual_dim, actual_dim);
857 for (
const auto &b :
vals.basis_values)
859 for (
const auto &g :
b.global)
861 for (
int d = 0; d < actual_dim; ++d)
862 deform_mat.row(d) += solution(
g.index * actual_dim + d) *
b.grad.row(n);
866 Eigen::MatrixXd trafo =
vals.
jac_it[n].inverse() + deform_mat;
867 normals.row(n) = normals.row(n) * trafo.inverse();
868 normals.row(n).normalize();
871 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
872 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, points, solution), tensor_flat);
877 const int g_index = v.global[0].index * actual_dim;
878 for (
int q = 0; q <
points.rows(); ++q)
880 assert(tensor_flat[0].first ==
"cauchy_stess");
881 Eigen::MatrixXd stress_tensor =
utils::unflatten(tensor_flat[0].second.row(q), actual_dim);
882 traction_forces.block(g_index, 0, actual_dim, 1) += stress_tensor * normals.row(q).transpose() * v.val(q) *
weights(q);
887 return traction_forces;
890 const auto append_traction_force = [&]() {
891 if (problem->is_scalar() || !explicit_fields || !options.export_field(
"traction_force"))
894 if (has_element_samples && sample.normals.rows() == sample.local_points.rows() && sample.primitive_ids.size() == sample.local_points.rows())
896 const Eigen::MatrixXd displaced_normals = displaced_output_normals(sample, solution);
897 const Eigen::MatrixXd &normals = displaced_normals.rows() == sample.normals.rows() ? displaced_normals : sample.normals;
898 Eigen::MatrixXd values = Eigen::MatrixXd::Zero(output_rows, actual_dim);
899 for (
int i = 0; i < sample.local_points.rows(); ++i)
901 const int element_id = sample.element_ids(i);
905 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
906 primary_assembler_->compute_tensor_value(
907 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
910 assert(tensor_flat[0].first ==
"cauchy_stess");
911 Eigen::Map<Eigen::MatrixXd> tensor(tensor_flat[0].second.data(), actual_dim, actual_dim);
912 values.row(i) = normals.row(i) * tensor;
915 const int primitive_id = sample.primitive_ids(i);
916 if (mesh_->is_volume())
918 if (mesh_->is_simplex(element_id))
919 area = mesh_->tri_area(primitive_id);
920 else if (mesh_->is_cube(element_id))
921 area = mesh_->quad_area(primitive_id);
922 else if (mesh_->is_prism(element_id))
923 area = mesh_->n_face_vertices(primitive_id) == 4 ? mesh_->quad_area(primitive_id) : mesh_->tri_area(primitive_id);
927 area = mesh_->edge_length(primitive_id);
929 values.row(i) *= area;
935 append_sampled_dof_field(
"traction_force", compute_traction_forces(), actual_dim);
938 append_scalar_values();
939 append_tensor_values();
940 append_averaged_values();
941 append_material_fields();
944 if ((paraview_options[
"jacobian_validity"] || (explicit_fields && options.export_field(
"validity")))
945 && has_element_samples
946 && sample.primitive_ids.size() == 0)
949 mesh_->dimension(),
space_.basis_list(),
space_.geometry_basis_list(), solution);
950 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
951 for (
int i = 0; i < sample.element_ids.size(); ++i)
953 validity(i) = std::find(
954 invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i))
955 != invalid_elements.end();
960 if (problem->is_time_dependent())
962 if (velocity && options.export_field(
"velocity"))
963 append_sampled_dof_field(
965 time_integrator ? time_integrator->v_prev() : Eigen::VectorXd::Zero(solution.size()),
967 if (acceleration && options.export_field(
"acceleration"))
968 append_sampled_dof_field(
970 time_integrator ? time_integrator->a_prev() : Eigen::VectorXd::Zero(solution.size()),
976 const double s = time_integrator ? time_integrator->acceleration_scaling() : 1;
977 for (
const auto &[name, form] : named_forms)
979 const std::string field_name = name +
"_forces";
980 if (!options.export_field(field_name))
983 Eigen::VectorXd force;
984 if (form && form->enabled())
986 form->first_derivative(solution, force);
991 force.setZero(solution.size());
993 append_sampled_dof_field(field_name, force, actual_dim);
997 append_traction_force();
999 if (explicit_fields && options.export_field(
"gradient_of_elastic_potential") && elastic_form)
1001 Eigen::VectorXd potential_grad;
1002 elastic_form->first_derivative(solution, potential_grad);
1003 append_sampled_dof_field(
"gradient_of_elastic_potential", potential_grad, actual_dim);
1006 if (explicit_fields && options.export_field(
"gradient_of_contact_potential") && contact_form && contact_form->weight() > 0)
1008 Eigen::VectorXd potential_grad;
1009 contact_form->first_derivative(solution, potential_grad);
1010 potential_grad *= -contact_form->barrier_stiffness() / contact_form->weight();
1011 append_sampled_dof_field(
"gradient_of_contact_potential", potential_grad, actual_dim);
1014 append_primary_output_fields(fields, sample, solution, options, obstacle);