412 const Eigen::MatrixXd &solution,
416 const std::vector<std::pair<std::string, std::shared_ptr<solver::Form>>> &named_forms,
420 std::vector<io::OutputField> fields;
427 const int actual_dim =
problem->is_scalar() ? 1 :
mesh_->dimension();
428 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
429 const bool material_params = paraview_options[
"material"];
430 const bool body_ids = paraview_options[
"body_ids"];
431 const bool velocity = paraview_options[
"velocity"];
432 const bool acceleration = paraview_options[
"acceleration"];
433 const bool forces = paraview_options[
"forces"] && !
problem->is_scalar();
434 const bool tensor_values = paraview_options[
"tensor_values"] && !
problem->is_scalar();
435 const bool scalar_values = paraview_options[
"scalar_values"];
436 const bool use_spline =
args[
"space"][
"basis_type"] ==
"Spline";
437 const bool explicit_fields = !options.
fields.empty();
439 const auto resize_to_output_rows = [&](Eigen::MatrixXd &values) {
440 if (output_rows <= values.rows())
443 const int previous_rows = values.rows();
444 values.conservativeResize(output_rows, values.cols());
445 values.bottomRows(output_rows - previous_rows).setZero();
448 const auto append_obstacle_values = [&](Eigen::MatrixXd &sampled_values,
const Eigen::MatrixXd &dof_values) ->
bool {
451 resize_to_output_rows(sampled_values);
452 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
455 const bool has_obstacle_rows =
457 && sample.
points.cols() == obstacle->
v().cols()
460 if (!has_obstacle_rows)
461 return sample.
points.rows() == 0 || sample.
points.rows() == sampled_values.rows();
463 sampled_values.conservativeResize(sampled_values.rows() + obstacle->
n_vertices(), sampled_values.cols());
464 if (dof_values.rows() >= obstacle->
ndof())
465 sampled_values.bottomRows(obstacle->
n_vertices()) =
468 sampled_values.bottomRows(obstacle->
n_vertices()).setZero();
472 const auto sample_dof_field = [&](
const Eigen::MatrixXd &dof_values,
const int field_dim, Eigen::MatrixXd &values) ->
bool {
473 if (dof_values.size() <= 0 || field_dim <= 0)
476 if (has_element_samples)
484 values.row(i).setZero();
488 Eigen::MatrixXd local_sol, local_grad;
491 element_id, sample.
local_points.row(i), dof_values, local_sol, local_grad);
493 for (
int d = 0; d < field_dim; ++d)
494 values(i, d) = local_sol(d);
497 return append_obstacle_values(values, dof_values);
502 values.resize(sample.
node_ids.size(), field_dim);
503 for (
int i = 0; i < sample.
node_ids.size(); ++i)
505 const int node_id = sample.
node_ids(i);
506 for (
int d = 0; d < field_dim; ++d)
508 const int dof = node_id * field_dim + d;
509 if (dof < 0 || dof >= dof_values.rows())
511 values(i, d) = dof_values(dof);
515 return sample.
points.rows() == 0 || sample.
points.rows() == values.rows();
521 const auto append_sampled_dof_field = [&](
const std::string &
name,
const Eigen::MatrixXd &dof_values,
const int field_dim) {
522 Eigen::MatrixXd values;
523 if (sample_dof_field(dof_values, field_dim, values))
527 const auto append_scalar_values = [&]() {
528 if (!scalar_values || problem->is_scalar() || !has_element_samples)
531 const bool wants_scalar = options.fields.empty()
532 || options.export_field(
"von_mises")
533 || options.export_field(
"von_mises_avg");
537 std::vector<assembler::Assembler::NamedMatrix> point_values;
538 for (
int i = 0; i < sample.local_points.rows(); ++i)
540 const int element_id = sample.element_ids(i);
544 std::vector<assembler::Assembler::NamedMatrix> local_values;
545 primary_assembler_->compute_scalar_value(
549 if (point_values.empty())
551 point_values.resize(local_values.size());
552 for (
int k = 0; k < local_values.size(); ++k)
554 point_values[k].first = local_values[k].first;
555 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
559 for (
int k = 0; k < local_values.size(); ++k)
560 point_values[k].second.row(i) = local_values[k].second;
563 for (
const auto &[name, values] : point_values)
565 if (options.export_field(name))
570 const auto append_tensor_values = [&]() {
571 if (!tensor_values || problem->is_scalar() || !has_element_samples)
574 const bool wants_tensor = options.fields.empty()
575 || options.export_field(
"cauchy_stess")
576 || options.export_field(
"pk1_stess")
577 || options.export_field(
"pk2_stess")
578 || options.export_field(
"F");
582 std::vector<assembler::Assembler::NamedMatrix> point_values;
583 for (
int i = 0; i < sample.local_points.rows(); ++i)
585 const int element_id = sample.element_ids(i);
589 std::vector<assembler::Assembler::NamedMatrix> local_values;
590 primary_assembler_->compute_tensor_value(
591 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
594 if (point_values.empty())
596 point_values.resize(local_values.size());
597 for (
int k = 0; k < local_values.size(); ++k)
599 point_values[k].first = local_values[k].first;
600 point_values[k].second.setZero(output_rows, local_values[k].second.cols());
604 for (
int k = 0; k < local_values.size(); ++k)
605 point_values[k].second.row(i) = local_values[k].second;
608 for (
const auto &[name, values] : point_values)
610 if (!options.export_field(name))
613 const int stride = mesh_->dimension();
614 assert(values.cols() % stride == 0);
615 for (
int i = 0; i < values.cols(); i += stride)
617 const int ii = (i / stride) + 1;
623 const auto append_averaged_values = [&]() {
624 if (use_spline || problem->is_scalar() || !has_element_samples || (!scalar_values && !tensor_values))
627 const bool wants_avg = options.fields.empty()
628 || options.export_field(
"von_mises_avg")
629 || options.export_field(
"cauchy_stess_avg")
630 || options.export_field(
"pk1_stess_avg")
631 || options.export_field(
"pk2_stess_avg")
632 || options.export_field(
"F_avg");
636 Eigen::MatrixXd areas(
space_.n_bases, 1);
638 std::vector<assembler::Assembler::NamedMatrix> tmp_s, tmp_t;
639 std::vector<Eigen::MatrixXd> avg_scalar, avg_tensor;
641 for (
int e = 0;
e < int(
space_.basis_list().size()); ++
e)
643 Eigen::MatrixXd local_pts;
644 if (mesh_->is_simplex(e))
646 if (mesh_->dimension() == 3)
651 else if (mesh_->is_cube(e))
653 if (mesh_->dimension() == 3)
658 else if (mesh_->is_prism(e))
667 const basis::ElementBases &bs =
space_.basis_list()[
e];
668 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
670 assembler::ElementAssemblyValues
vals;
675 primary_assembler_->compute_scalar_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_s);
677 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, local_pts, solution), tmp_t);
679 if (avg_scalar.empty() && !tmp_s.empty())
681 avg_scalar.resize(tmp_s.size());
682 for (
auto &m : avg_scalar)
683 m.setZero(
space_.n_bases, 1);
685 if (avg_tensor.empty() && !tmp_t.empty())
687 avg_tensor.resize(tmp_t.size());
688 for (
auto &m : avg_tensor)
689 m.setZero(
space_.n_bases, actual_dim * actual_dim);
692 for (
size_t j = 0; j < bs.bases.size(); ++j)
694 const basis::Basis &
b = bs.bases[j];
695 if (
b.global().size() > 1)
698 const int index =
b.global().front().index;
699 areas(index) += area;
700 for (
int k = 0; k < tmp_s.size(); ++k)
701 avg_scalar[k](index) += tmp_s[k].second(j) * area;
702 for (
int k = 0; k < tmp_t.size(); ++k)
703 avg_tensor[k].row(index) += tmp_t[k].second.row(j) * area;
707 for (
auto &m : avg_scalar)
708 for (int i = 0; i < m.rows(); ++i)
711 for (
auto &m : avg_tensor)
712 for (int i = 0; i < m.rows(); ++i)
714 m.row(i) /= areas(i);
716 for (
int k = 0; k < tmp_s.size(); ++k)
718 const std::string name = fmt::format(
"{:s}_avg", tmp_s[k].first);
719 if (!options.export_field(name))
722 Eigen::MatrixXd sampled;
723 if (sample_dof_field(avg_scalar[k], 1, sampled))
727 for (
int k = 0; k < tmp_t.size(); ++k)
729 const std::string base_name = fmt::format(
"{:s}_avg", tmp_t[k].first);
730 if (!options.export_field(base_name))
733 Eigen::MatrixXd sampled;
734 if (!sample_dof_field(
utils::flatten(avg_tensor[k]), actual_dim * actual_dim, sampled))
737 const int stride = mesh_->dimension();
738 for (
int i = 0; i < sampled.cols(); i += stride)
740 const int ii = (i / stride) + 1;
746 const auto append_material_fields = [&]() {
747 if (!material_params || !has_element_samples)
750 const auto ¶ms = primary_assembler_->parameters();
751 std::map<std::string, Eigen::MatrixXd> param_values;
752 for (
const auto &[p, _] : params)
753 param_values[p].setZero(output_rows, 1);
754 Eigen::MatrixXd rhos = Eigen::MatrixXd::Zero(output_rows, 1);
756 const auto &density = mass_assembler_->density();
757 for (
int i = 0; i < sample.local_points.rows(); ++i)
759 const int element_id = sample.element_ids(i);
763 for (
const auto &[p, func] : params)
764 param_values.at(p)(i) =
func(sample.local_points.row(i), sample.
points.row(i), sample.time, element_id);
765 rhos(i) = density(sample.local_points.row(i), sample.points.row(i), sample.time, element_id);
768 for (
const auto &[name, values] : param_values)
769 if (options.export_field(name))
771 if (options.export_field(
"rho"))
775 const auto append_body_ids = [&]() {
776 if (!body_ids || !options.export_field(
"body_ids") || !has_element_samples)
779 Eigen::MatrixXd ids = Eigen::MatrixXd::Zero(output_rows, 1);
780 for (
int i = 0; i < sample.element_ids.size(); ++i)
782 const int element_id = sample.element_ids(i);
784 ids(i) = mesh_->get_body_id(element_id);
789 const auto compute_traction_forces = [&]() {
790 Eigen::MatrixXd traction_forces;
791 traction_forces.setZero(
space_.n_bases * actual_dim, 1);
793 Eigen::MatrixXd uv,
points, normals;
794 Eigen::VectorXd weights;
795 Eigen::VectorXi global_primitive_ids;
796 assembler::ElementAssemblyValues
vals;
798 for (
const auto &lb : boundary_.total_local_boundary)
800 const int e = lb.element_id();
802 lb, elastic_boundary_samples(), *mesh_,
false, uv, points, normals, weights, global_primitive_ids);
806 const basis::ElementBases &bs =
space_.basis_list()[
e];
807 const basis::ElementBases &gbs =
space_.geometry_basis_list()[
e];
808 vals.
compute(e, mesh_->is_volume(), points, bs, gbs);
810 for (
int n = 0; n < normals.rows(); ++n)
812 Eigen::MatrixXd deform_mat = Eigen::MatrixXd::Zero(actual_dim, actual_dim);
813 for (
const auto &b :
vals.basis_values)
815 for (
const auto &g :
b.global)
817 for (
int d = 0; d < actual_dim; ++d)
818 deform_mat.row(d) += solution(
g.index * actual_dim + d) *
b.grad.row(n);
822 Eigen::MatrixXd trafo =
vals.
jac_it[n].inverse() + deform_mat;
823 normals.row(n) = normals.row(n) * trafo.inverse();
824 normals.row(n).normalize();
827 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
828 primary_assembler_->compute_tensor_value(assembler::OutputData(sample.time, e, bs, gbs, points, solution), tensor_flat);
833 const int g_index = v.global[0].index * actual_dim;
834 for (
int q = 0; q <
points.rows(); ++q)
836 assert(tensor_flat[0].first ==
"cauchy_stess");
837 Eigen::MatrixXd stress_tensor =
utils::unflatten(tensor_flat[0].second.row(q), actual_dim);
838 traction_forces.block(g_index, 0, actual_dim, 1) += stress_tensor * normals.row(q).transpose() * v.val(q) * weights(q);
843 return traction_forces;
846 const auto append_traction_force = [&]() {
847 if (problem->is_scalar() || !explicit_fields || !options.export_field(
"traction_force"))
850 if (has_element_samples && sample.normals.rows() == sample.local_points.rows() && sample.primitive_ids.size() == sample.local_points.rows())
852 const Eigen::MatrixXd displaced_normals = displaced_output_normals(sample, solution);
853 const Eigen::MatrixXd &normals = displaced_normals.rows() == sample.normals.rows() ? displaced_normals : sample.normals;
854 Eigen::MatrixXd values = Eigen::MatrixXd::Zero(output_rows, actual_dim);
855 for (
int i = 0; i < sample.local_points.rows(); ++i)
857 const int element_id = sample.element_ids(i);
861 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
862 primary_assembler_->compute_tensor_value(
863 assembler::OutputData(sample.time, element_id,
space_.basis_list()[element_id],
space_.geometry_basis_list()[element_id], sample.local_points.row(i), solution),
866 assert(tensor_flat[0].first ==
"cauchy_stess");
867 Eigen::Map<Eigen::MatrixXd> tensor(tensor_flat[0].second.data(), actual_dim, actual_dim);
868 values.row(i) = normals.row(i) * tensor;
871 const int primitive_id = sample.primitive_ids(i);
872 if (mesh_->is_volume())
874 if (mesh_->is_simplex(element_id))
875 area = mesh_->tri_area(primitive_id);
876 else if (mesh_->is_cube(element_id))
877 area = mesh_->quad_area(primitive_id);
878 else if (mesh_->is_prism(element_id))
879 area = mesh_->n_face_vertices(primitive_id) == 4 ? mesh_->quad_area(primitive_id) : mesh_->tri_area(primitive_id);
883 area = mesh_->edge_length(primitive_id);
885 values.row(i) *= area;
891 append_sampled_dof_field(
"traction_force", compute_traction_forces(), actual_dim);
894 append_scalar_values();
895 append_tensor_values();
896 append_averaged_values();
897 append_material_fields();
900 if ((paraview_options[
"jacobian_validity"] || (explicit_fields && options.export_field(
"validity")))
901 && has_element_samples
902 && sample.primitive_ids.size() == 0)
905 mesh_->dimension(),
space_.basis_list(),
space_.geometry_basis_list(), solution);
906 Eigen::MatrixXd validity = Eigen::MatrixXd::Zero(output_rows, 1);
907 for (
int i = 0; i < sample.element_ids.size(); ++i)
909 validity(i) = std::find(
910 invalid_elements.begin(), invalid_elements.end(), sample.element_ids(i))
911 != invalid_elements.end();
916 if (problem->is_time_dependent())
918 if (velocity && options.export_field(
"velocity"))
919 append_sampled_dof_field(
921 time_integrator ? time_integrator->v_prev() : Eigen::VectorXd::Zero(solution.size()),
923 if (acceleration && options.export_field(
"acceleration"))
924 append_sampled_dof_field(
926 time_integrator ? time_integrator->a_prev() : Eigen::VectorXd::Zero(solution.size()),
932 const double s = time_integrator ? time_integrator->acceleration_scaling() : 1;
933 for (
const auto &[name, form] : named_forms)
935 const std::string field_name = name +
"_forces";
936 if (!options.export_field(field_name))
939 Eigen::VectorXd force;
940 if (form && form->enabled())
942 form->first_derivative(solution, force);
947 force.setZero(solution.size());
949 append_sampled_dof_field(field_name, force, actual_dim);
953 append_traction_force();
955 if (explicit_fields && options.export_field(
"gradient_of_elastic_potential") && elastic_form)
957 Eigen::VectorXd potential_grad;
958 elastic_form->first_derivative(solution, potential_grad);
959 append_sampled_dof_field(
"gradient_of_elastic_potential", potential_grad, actual_dim);
962 if (explicit_fields && options.export_field(
"gradient_of_contact_potential") && contact_form && contact_form->weight() > 0)
964 Eigen::VectorXd potential_grad;
965 contact_form->first_derivative(solution, potential_grad);
966 potential_grad *= -contact_form->barrier_stiffness() / contact_form->weight();
967 append_sampled_dof_field(
"gradient_of_contact_potential", potential_grad, actual_dim);
970 append_primary_output_fields(fields, sample, solution, options, obstacle);