451 const Eigen::MatrixXd &solution,
455 const std::vector<std::pair<std::string, std::shared_ptr<solver::Form>>> &named_forms,
459 std::vector<io::OutputField> fields;
466 const int actual_dim =
problem->is_scalar() ? 1 :
mesh_->dimension();
467 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
468 const bool material_params = paraview_options[
"material"];
469 const bool body_ids = paraview_options[
"body_ids"];
470 const bool velocity = paraview_options[
"velocity"];
471 const bool acceleration = paraview_options[
"acceleration"];
472 const bool forces = paraview_options[
"forces"] && !
problem->is_scalar();
473 const bool tensor_values = paraview_options[
"tensor_values"] && !
problem->is_scalar();
474 const bool scalar_values = paraview_options[
"scalar_values"];
475 const bool use_spline =
args[
"space"][
"basis_type"] ==
"Spline";
476 const bool explicit_fields = !options.
fields.empty();
478 const auto resize_to_output_rows = [&](Eigen::MatrixXd &values) {
479 if (output_rows <= values.rows())
482 const int previous_rows = values.rows();
483 values.conservativeResize(output_rows, values.cols());
484 values.bottomRows(output_rows - previous_rows).setZero();
487 const auto append_obstacle_values = [&](Eigen::MatrixXd &sampled_values,
const Eigen::MatrixXd &dof_values) ->
bool {
490 resize_to_output_rows(sampled_values);
491 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
494 const bool has_obstacle_rows =
496 && sample.
points.cols() == obstacle->
v().cols()
499 if (!has_obstacle_rows)
500 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
502 sampled_values.conservativeResize(sampled_values.rows() + obstacle->
n_vertices(), sampled_values.cols());
503 if (dof_values.rows() >= obstacle->
ndof())
504 sampled_values.bottomRows(obstacle->
n_vertices()) =
507 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
511 const auto sample_dof_field = [&](
const Eigen::MatrixXd &dof_values,
const int field_dim, Eigen::MatrixXd &values) ->
bool {
512 if (dof_values.size() <= 0 || field_dim <= 0)
515 if (has_element_samples)
523 values.row(i).setZero();
527 Eigen::MatrixXd local_sol, local_grad;
530 element_id, sample.
local_points.row(i), dof_values, local_sol, local_grad);
532 for (
int d = 0; d < field_dim; ++d)
533 values(i, d) = local_sol(d);
536 return append_obstacle_values(values, dof_values);
541 values.resize(sample.
node_ids.size(), field_dim);
542 for (
int i = 0; i < sample.
node_ids.size(); ++i)
544 const int node_id = sample.
node_ids(i);
545 for (
int d = 0; d < field_dim; ++d)
547 const int dof = node_id * field_dim + d;
548 if (dof < 0 || dof >= dof_values.rows())
550 values(i, d) = dof_values(dof);
554 return sample.
points.rows() == 0 || sample.
points.rows() == values.rows();
560 const auto append_sampled_dof_field = [&](
const std::string &
name,
const Eigen::MatrixXd &dof_values,
const int field_dim) {
561 Eigen::MatrixXd values;
562 if (sample_dof_field(dof_values, field_dim, values))
566 const auto append_scalar_values = [&]() {
567 if (!scalar_values || problem->is_scalar() || !has_element_samples)
570 const bool wants_scalar = options.fields.empty()
571 || options.export_field(
"von_mises")
572 || options.export_field(
"von_mises_avg");
576 std::vector<assembler::Assembler::NamedMatrix> point_values;
577 for (
int i = 0; i < sample.local_points.rows(); ++i)
579 const int element_id = sample.element_ids(i);
583 std::vector<assembler::Assembler::NamedMatrix> local_values;
584 primary_assembler_->compute_scalar_value(
588 if (point_values.empty())
590 point_values.resize(local_values.size());
591 for (
int k = 0; k < local_values.size(); ++k)
593 point_values[k].first = local_values[k].first;
594 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
598 for (
int k = 0; k < local_values.size(); ++k)
599 point_values[k].second.row(i) = local_values[k].second;
602 for (
const auto &[name, values] : point_values)
604 if (options.export_field(name))
609 const auto append_tensor_values = [&]() {
610 if (!tensor_values || problem->is_scalar() || !has_element_samples)
613 const bool wants_tensor = options.fields.empty()
614 || options.export_field(
"cauchy_stess")
615 || options.export_field(
"pk1_stess")
616 || options.export_field(
"pk2_stess")
617 || options.export_field(
"F");
621 std::vector<assembler::Assembler::NamedMatrix> point_values;
622 for (
int i = 0; i < sample.local_points.rows(); ++i)
624 const int element_id = sample.element_ids(i);
628 std::vector<assembler::Assembler::NamedMatrix> local_values;
629 primary_assembler_->compute_tensor_value(
630 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
633 if (point_values.empty())
635 point_values.resize(local_values.size());
636 for (
int k = 0; k < local_values.size(); ++k)
638 point_values[k].first = local_values[k].first;
639 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
643 for (
int k = 0; k < local_values.size(); ++k)
644 point_values[k].second.row(i) = local_values[k].second;
647 for (
const auto &[name, values] : point_values)
649 if (!options.export_field(name))
652 const int stride = mesh_->dimension();
653 assert(values.cols() % stride == 0);
654 for (
int i = 0; i < values.cols(); i += stride)
656 const int ii = (i / stride) + 1;
662 const auto append_averaged_values = [&]() {
663 if (use_spline || problem->is_scalar() || !has_element_samples || (!scalar_values && !tensor_values))
666 const bool wants_avg = options.fields.empty()
667 || options.export_field(
"von_mises_avg")
668 || options.export_field(
"cauchy_stess_avg")
669 || options.export_field(
"pk1_stess_avg")
670 || options.export_field(
"pk2_stess_avg")
671 || options.export_field(
"F_avg");
675 Eigen::MatrixXd areas(
space_.n_bases, 1);
677 std::vector<assembler::Assembler::NamedMatrix> tmp_s, tmp_t;
678 std::vector<Eigen::MatrixXd> avg_scalar, avg_tensor;
680 for (
int e = 0;
e < int(
space_.basis_list().size()); ++
e)
682 Eigen::MatrixXd local_pts;
683 if (mesh_->is_simplex(e))
685 if (mesh_->dimension() == 3)
690 else if (mesh_->is_cube(e))
692 if (mesh_->dimension() == 3)
697 else if (mesh_->is_prism(e))
706 const basis::ElementBases &bs =
space_.basis_list()[
e];
707 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
709 assembler::ElementAssemblyValues
vals;
714 primary_assembler_->compute_scalar_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_s);
716 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_t);
718 if (avg_scalar.empty() && !tmp_s.empty())
720 avg_scalar.resize(tmp_s.size());
721 for (
auto &m : avg_scalar)
722 m.setZero(
space_.n_bases, 1);
724 if (avg_tensor.empty() && !tmp_t.empty())
726 avg_tensor.resize(tmp_t.size());
727 for (
auto &m : avg_tensor)
728 m.setZero(
space_.n_bases, actual_dim * actual_dim);
731 for (
size_t j = 0; j < bs.bases.size(); ++j)
733 const basis::Basis &
b = bs.bases[j];
734 if (
b.global().size() > 1)
737 const int index =
b.global().front().index;
738 areas(index) += area;
739 for (
int k = 0; k < tmp_s.size(); ++k)
740 avg_scalar[k](index) += tmp_s[k].second(j) * area;
741 for (
int k = 0; k < tmp_t.size(); ++k)
742 avg_tensor[k].row(index) += tmp_t[k].second.row(j) * area;
746 for (
auto &m : avg_scalar)
747 for (int i = 0; i < m.rows(); ++i)
750 for (
auto &m : avg_tensor)
751 for (int i = 0; i < m.rows(); ++i)
753 m.row(i) /= areas(i);
755 for (
int k = 0; k < tmp_s.size(); ++k)
757 const std::string name = fmt::format(
"{:s}_avg", tmp_s[k].first);
758 if (!options.export_field(name))
761 Eigen::MatrixXd sampled;
762 if (sample_dof_field(avg_scalar[k], 1, sampled))
766 for (
int k = 0; k < tmp_t.size(); ++k)
768 const std::string base_name = fmt::format(
"{:s}_avg", tmp_t[k].first);
769 if (!options.export_field(base_name))
772 Eigen::MatrixXd sampled;
773 if (!sample_dof_field(
utils::flatten(avg_tensor[k]), actual_dim * actual_dim, sampled))
776 const int stride = mesh_->dimension();
777 for (
int i = 0; i < sampled.cols(); i += stride)
779 const int ii = (i / stride) + 1;
785 const auto append_material_fields = [&]() {
786 if (!material_params || !has_element_samples)
789 const auto ¶ms = primary_assembler_->parameters();
790 std::map<std::string, Eigen::MatrixXd> param_values;
791 for (
const auto &[p, _] : params)
792 param_values[p].setZero(output_rows, 1);
793 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
795 const auto &density = mass_assembler_->density();
796 for (
int i = 0; i < sample.local_points.rows(); ++i)
798 const int element_id = sample.element_ids(i);
802 for (
const auto &[p, func] : params)
803 param_values.at(p)(i) =
func(sample.local_points.row(i), sample.
points.row(i), sample.time, element_id);
804 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
807 for (
const auto &[name, values] : param_values)
808 if (options.export_field(name))
810 if (options.export_field(
"rho"))
814 const auto append_body_ids = [&]() {
815 if (!body_ids || !options.export_field(
"body_ids") || !has_element_samples)
818 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
819 for (
int i = 0; i < sample.element_ids.size(); ++i)
821 const int element_id = sample.element_ids(i);
823 ids(i) = mesh_->get_body_id(element_id);
828 const auto compute_traction_forces = [&]() {
829 Eigen::MatrixXd traction_forces;
830 traction_forces.setZero(
space_.n_bases * actual_dim, 1);
832 Eigen::MatrixXd uv,
points, normals;
834 Eigen::VectorXi global_primitive_ids;
835 assembler::ElementAssemblyValues
vals;
837 for (
const auto &lb : boundary_.total_local_boundary)
839 const int e = lb.element_id();
841 lb, elastic_boundary_samples(), *mesh_,
false, uv, points, normals,
weights, global_primitive_ids);
845 const basis::ElementBases &bs =
space_.basis_list()[
e];
846 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
847 vals.
compute(e, mesh_->is_volume(), points, bs, gbs);
849 for (
int n = 0; n < normals.rows(); ++n)
851 Eigen::MatrixXd deform_mat = Eigen::MatrixXd::Zero(actual_dim, actual_dim);
852 for (
const auto &b :
vals.basis_values)
854 for (
const auto &g :
b.global)
856 for (
int d = 0; d < actual_dim; ++d)
857 deform_mat.row(d) += solution(
g.index * actual_dim + d) *
b.grad.row(n);
861 Eigen::MatrixXd trafo =
vals.
jac_it[n].inverse() + deform_mat;
862 normals.row(n) = normals.row(n) * trafo.inverse();
863 normals.row(n).normalize();
866 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
867 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, points, solution), tensor_flat);
872 const int g_index = v.global[0].index * actual_dim;
873 for (
int q = 0; q <
points.rows(); ++q)
875 assert(tensor_flat[0].first ==
"cauchy_stess");
876 Eigen::MatrixXd stress_tensor =
utils::unflatten(tensor_flat[0].second.row(q), actual_dim);
877 traction_forces.block(g_index, 0, actual_dim, 1) += stress_tensor * normals.row(q).transpose() * v.val(q) *
weights(q);
882 return traction_forces;
885 const auto append_traction_force = [&]() {
886 if (problem->is_scalar() || !explicit_fields || !options.export_field(
"traction_force"))
889 if (has_element_samples && sample.normals.rows() == sample.local_points.rows() && sample.primitive_ids.size() == sample.local_points.rows())
891 const Eigen::MatrixXd displaced_normals = displaced_output_normals(sample, solution);
892 const Eigen::MatrixXd &normals = displaced_normals.rows() == sample.normals.rows() ? displaced_normals : sample.normals;
893 Eigen::MatrixXd values = Eigen::MatrixXd::Zero(output_rows, actual_dim);
894 for (
int i = 0; i < sample.local_points.rows(); ++i)
896 const int element_id = sample.element_ids(i);
900 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
901 primary_assembler_->compute_tensor_value(
902 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
905 assert(tensor_flat[0].first ==
"cauchy_stess");
906 Eigen::Map<Eigen::MatrixXd> tensor(tensor_flat[0].second.data(), actual_dim, actual_dim);
907 values.row(i) = normals.row(i) * tensor;
910 const int primitive_id = sample.primitive_ids(i);
911 if (mesh_->is_volume())
913 if (mesh_->is_simplex(element_id))
914 area = mesh_->tri_area(primitive_id);
915 else if (mesh_->is_cube(element_id))
916 area = mesh_->quad_area(primitive_id);
917 else if (mesh_->is_prism(element_id))
918 area = mesh_->n_face_vertices(primitive_id) == 4 ? mesh_->quad_area(primitive_id) : mesh_->tri_area(primitive_id);
922 area = mesh_->edge_length(primitive_id);
924 values.row(i) *= area;
930 append_sampled_dof_field(
"traction_force", compute_traction_forces(), actual_dim);
933 append_scalar_values();
934 append_tensor_values();
935 append_averaged_values();
936 append_material_fields();
939 if ((paraview_options[
"jacobian_validity"] || (explicit_fields && options.export_field(
"validity")))
940 && has_element_samples
941 && sample.primitive_ids.size() == 0)
944 mesh_->dimension(),
space_.basis_list(),
space_.geometry_basis_list(), solution);
945 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
946 for (
int i = 0; i < sample.element_ids.size(); ++i)
948 validity(i) = std::find(
949 invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i))
950 != invalid_elements.end();
955 if (problem->is_time_dependent())
957 if (velocity && options.export_field(
"velocity"))
958 append_sampled_dof_field(
960 time_integrator ? time_integrator->v_prev() : Eigen::VectorXd::Zero(solution.size()),
962 if (acceleration && options.export_field(
"acceleration"))
963 append_sampled_dof_field(
965 time_integrator ? time_integrator->a_prev() : Eigen::VectorXd::Zero(solution.size()),
971 const double s = time_integrator ? time_integrator->acceleration_scaling() : 1;
972 for (
const auto &[name, form] : named_forms)
974 const std::string field_name = name +
"_forces";
975 if (!options.export_field(field_name))
978 Eigen::VectorXd force;
979 if (form && form->enabled())
981 form->first_derivative(solution, force);
986 force.setZero(solution.size());
988 append_sampled_dof_field(field_name, force, actual_dim);
992 append_traction_force();
994 if (explicit_fields && options.export_field(
"gradient_of_elastic_potential") && elastic_form)
996 Eigen::VectorXd potential_grad;
997 elastic_form->first_derivative(solution, potential_grad);
998 append_sampled_dof_field(
"gradient_of_elastic_potential", potential_grad, actual_dim);
1001 if (explicit_fields && options.export_field(
"gradient_of_contact_potential") && contact_form && contact_form->weight() > 0)
1003 Eigen::VectorXd potential_grad;
1004 contact_form->first_derivative(solution, potential_grad);
1005 potential_grad *= -contact_form->barrier_stiffness() / contact_form->weight();
1006 append_sampled_dof_field(
"gradient_of_contact_potential", potential_grad, actual_dim);
1009 append_primary_output_fields(fields, sample, solution, options, obstacle);