116 const double target_edge_length,
117 const double max_amips_energy,
118 const int64_t passes,
119 const double envelope_size,
120 const bool intermediate_output,
121 std::vector<attribute::MeshAttributeHandle>& pass_through_attributes,
122 std::string output_dir,
125 auto position_handle =
127 auto surface_position_handle =
133 "periodic mesh #v: {}",
136 "position mesh #v: {}",
139 Eigen::Vector3d bmin, bmax;
140 bmin.setConstant(std::numeric_limits<double>::max());
141 bmax.setConstant(std::numeric_limits<double>::lowest());
145 const auto p = position_accessor.vector_attribute(v);
146 for (int64_t d = 0; d < bmax.size(); ++d) {
147 bmin[d] = std::min(bmin[d], p[d]);
148 bmax[d] = std::max(bmax[d], p[d]);
152 const double bbdiag = (bmax - bmin).norm();
153 const double target_edge_length_abs = bbdiag * target_edge_length;
155 const double period_x = bmax[0] - bmin[0];
156 const double period_y = bmax[1] - bmin[1];
157 const double period_z = bmax[2] - bmin[2];
164 auto envelope_invariant = std::make_shared<EnvelopeInvariant>(
165 surface_position_handle,
166 std::sqrt(2) * envelope_size,
167 surface_position_handle);
169 auto propagate_to_child_position =
170 [](
const Eigen::MatrixX<double>& P) -> Eigen::VectorX<double> {
return P; };
172 auto propagate_to_parent_position =
173 [](
const Eigen::MatrixX<double>& P) -> Eigen::VectorX<double> {
174 assert(P.cols() == 1);
178 auto update_parent_position = std::make_shared<SingleAttributeTransferStrategy<double, double>>(
180 surface_position_handle,
181 propagate_to_parent_position);
183 auto update_child_position = std::make_shared<SingleAttributeTransferStrategy<double, double>>(
184 surface_position_handle,
186 propagate_to_child_position);
194 auto amips_accessor = position_mesh.
create_accessor(amips_handle.as<
double>());
196 auto compute_amips = [](
const Eigen::MatrixXd& P) -> Eigen::VectorXd {
198 std::array<double, 12> pts;
199 for (
size_t i = 0; i < 4; ++i) {
200 for (
size_t j = 0; j < 3; ++j) {
201 pts[3 * i + j] = P(j, i);
205 return Eigen::VectorXd::Constant(1, a);
208 std::make_shared<wmtk::operations::SingleAttributeTransferStrategy<double, double>>(
212 amips_update->run_on_all();
214 double max_amips = std::numeric_limits<double>::lowest();
215 double min_amips = std::numeric_limits<double>::max();
218 double e = amips_accessor.scalar_attribute(t);
219 max_amips = std::max(max_amips, e);
220 min_amips = std::min(min_amips, e);
223 logger().info(
"Initial Max AMIPS Energy: {}, Min AMIPS Energy: {}", max_amips, min_amips);
230 "target_edge_length",
234 target_edge_length_abs);
240 auto edge_length_handle =
242 auto edge_length_accessor = position_mesh.
create_accessor(edge_length_handle.as<
double>());
244 auto compute_edge_length = [](
const Eigen::MatrixXd& P) -> Eigen::VectorXd {
245 return Eigen::VectorXd::Constant(1, sqrt((P.col(0) - P.col(1)).squaredNorm()));
247 auto edge_length_update =
248 std::make_shared<wmtk::operations::SingleAttributeTransferStrategy<double, double>>(
251 compute_edge_length);
252 edge_length_update->run_on_all();
259 "sizing_field_scalar",
268 auto visited_vertex_flag_handle =
275 auto energy_filter_handle =
279 auto energy_filter_accessor = position_mesh.
create_accessor<
char>(energy_filter_handle);
281 auto update_energy_filter_func = [](
const Eigen::MatrixX<double>& P) -> Eigen::VectorX<char> {
282 return Eigen::VectorX<char>::Constant(1,
char(1));
284 auto energy_filter_update =
285 std::make_shared<wmtk::operations::SingleAttributeTransferStrategy<char, double>>(
286 energy_filter_handle,
288 update_energy_filter_func);
293 auto visited_edge_flag_handle =
297 auto update_flag_func = [](
const Eigen::MatrixX<double>& P) -> Eigen::VectorX<char> {
298 return Eigen::VectorX<char>::Constant(1,
char(1));
301 std::make_shared<wmtk::operations::SingleAttributeTransferStrategy<char, double>>(
302 visited_edge_flag_handle,
309 pass_through_attributes.push_back(edge_length_handle);
310 pass_through_attributes.push_back(amips_handle);
311 pass_through_attributes.push_back(visited_vertex_flag_handle);
312 pass_through_attributes.push_back(energy_filter_handle);
313 pass_through_attributes.push_back(surface_position_handle);
319 auto inversion_invariant = std::make_shared<SimplexInversionInvariant<double>>(
321 position_handle.as<
double>());
323 std::shared_ptr<function::PerSimplexFunction> amips =
324 std::make_shared<AMIPS>(position_mesh, position_handle);
326 auto function_invariant =
327 std::make_shared<MaxFunctionInvariant>(position_mesh.
top_simplex_type(), amips);
329 auto link_condition = std::make_shared<MultiMeshLinkConditionInvariant>(position_mesh);
331 auto todo_larger = std::make_shared<TodoLargerInvariant>(
333 edge_length_handle.as<
double>(),
334 target_edge_length_handle.as<
double>(),
337 auto todo_smaller = std::make_shared<TodoSmallerInvariant>(
339 edge_length_handle.as<
double>(),
340 target_edge_length_handle.as<
double>(),
343 auto valence_3 = std::make_shared<EdgeValenceInvariant>(position_mesh, 3);
344 auto valence_4 = std::make_shared<EdgeValenceInvariant>(position_mesh, 4);
345 auto valence_5 = std::make_shared<EdgeValenceInvariant>(position_mesh, 5);
347 auto invariant_separate_substructures =
348 std::make_shared<invariants::SeparateSubstructuresInvariant>(position_mesh);
354 std::vector<std::shared_ptr<Operation>> ops;
355 std::vector<std::string> ops_name;
359 return -edge_length_accessor.scalar_attribute(s.tuple());
363 return edge_length_accessor.scalar_attribute(s.tuple());
369 auto split = std::make_shared<EdgeSplit>(position_mesh);
370 split->set_priority(long_edges_first);
372 split->add_invariant(todo_larger);
373 split->add_invariant(
374 std::make_shared<EnergyFilterInvariant>(position_mesh, energy_filter_handle.as<
char>()));
375 split->add_invariant(inversion_invariant);
377 split->set_new_attribute_strategy(position_handle);
378 split->set_new_attribute_strategy(sizing_field_scalar_handle);
379 split->set_new_attribute_strategy(
380 visited_edge_flag_handle,
383 split->set_new_attribute_strategy(
384 target_edge_length_handle,
388 for (
const auto& attr : pass_through_attributes) {
389 split->set_new_attribute_strategy(
395 split->add_transfer_strategy(amips_update);
396 split->add_transfer_strategy(edge_length_update);
397 split->add_transfer_strategy(tag_update);
398 split->add_transfer_strategy(energy_filter_update);
399 split->add_transfer_strategy(update_child_position);
401 ops.emplace_back(split);
402 ops_name.emplace_back(
"SPLIT");
408 auto clps_strat1 = std::make_shared<CollapseNewAttributeStrategy<double>>(position_handle);
409 clps_strat1->set_strategy(CollapseBasicStrategy::CopyOther);
411 auto clps_strat2 = std::make_shared<CollapseNewAttributeStrategy<double>>(position_handle);
412 clps_strat2->set_strategy(CollapseBasicStrategy::CopyTuple);
414 auto setup_collapse = [&](std::shared_ptr<EdgeCollapse>& collapse) {
415 collapse->add_invariant(invariant_separate_substructures);
417 collapse->add_invariant(inversion_invariant);
418 collapse->add_invariant(envelope_invariant);
420 collapse->set_new_attribute_strategy(
421 visited_edge_flag_handle,
424 collapse->add_transfer_strategy(tag_update);
425 collapse->add_transfer_strategy(energy_filter_update);
426 for (
const auto& attr : pass_through_attributes) {
427 collapse->set_new_attribute_strategy(
431 collapse->set_new_attribute_strategy(
432 target_edge_length_handle,
435 collapse->add_transfer_strategy(amips_update);
436 collapse->add_transfer_strategy(edge_length_update);
438 collapse->add_transfer_strategy(update_child_position);
441 auto collapse1 = std::make_shared<EdgeCollapse>(position_mesh);
442 collapse1->add_invariant(std::make_shared<CollapseEnergyBeforeInvariantDouble>(
444 position_handle.as<
double>(),
445 amips_handle.as<
double>(),
448 collapse1->set_new_attribute_strategy(position_handle, clps_strat1);
449 collapse1->set_new_attribute_strategy(sizing_field_scalar_handle, clps_strat1);
450 setup_collapse(collapse1);
452 auto collapse2 = std::make_shared<EdgeCollapse>(position_mesh);
453 collapse2->add_invariant(std::make_shared<CollapseEnergyBeforeInvariantDouble>(
455 position_handle.as<
double>(),
456 amips_handle.as<
double>(),
459 collapse2->set_new_attribute_strategy(position_handle, clps_strat2);
460 collapse2->set_new_attribute_strategy(sizing_field_scalar_handle, clps_strat2);
461 setup_collapse(collapse2);
463 auto collapse = std::make_shared<OrOperationSequence>(position_mesh);
464 collapse->add_operation(collapse1);
465 collapse->add_operation(collapse2);
466 collapse->set_priority(short_edges_first);
467 collapse->add_invariant(
468 std::make_shared<EnergyFilterInvariant>(position_mesh, energy_filter_handle.as<
char>()));
469 collapse->add_invariant(todo_smaller);
471 collapse->add_transfer_strategy(update_child_position);
473 ops.emplace_back(collapse);
474 ops_name.emplace_back(
"COLLAPSE");
482 auto swap56 = std::make_shared<MinOperationSequence>(position_mesh);
483 for (
int i = 0; i < 5; ++i) {
484 auto swap = std::make_shared<TetEdgeSwap>(position_mesh, i);
485 swap->collapse().add_invariant(invariant_separate_substructures);
487 swap->collapse().set_new_attribute_strategy(
489 CollapseBasicStrategy::CopyOther);
490 swap->collapse().set_new_attribute_strategy(
491 sizing_field_scalar_handle,
492 CollapseBasicStrategy::CopyOther);
493 swap->split().set_new_attribute_strategy(position_handle);
494 swap->split().set_new_attribute_strategy(sizing_field_scalar_handle);
496 swap->split().set_new_attribute_strategy(
497 visited_edge_flag_handle,
500 swap->collapse().set_new_attribute_strategy(
501 visited_edge_flag_handle,
504 swap->split().set_new_attribute_strategy(
505 target_edge_length_handle,
508 swap->collapse().set_new_attribute_strategy(
509 target_edge_length_handle,
512 swap->add_invariant(std::make_shared<Swap56EnergyBeforeInvariantDouble>(
514 position_handle.as<
double>(),
517 swap->add_transfer_strategy(amips_update);
519 swap->collapse().add_invariant(inversion_invariant);
521 swap->collapse().add_invariant(envelope_invariant);
523 for (
const auto& attr : pass_through_attributes) {
524 swap->split().set_new_attribute_strategy(
528 swap->collapse().set_new_attribute_strategy(
533 swap56->add_operation(swap);
536 auto swap56_energy_check = [&](int64_t idx,
const simplex::Simplex& t) ->
double {
544 const Tuple e0 = t.tuple();
547 std::array<Tuple, 5> v;
548 auto iter_tuple = e0;
549 for (int64_t i = 0; i < 5; ++i) {
553 if (iter_tuple != e0)
return 0;
554 assert(iter_tuple == e0);
558 std::array<Eigen::Vector3<double>, 7> positions = {
559 {accessor.const_vector_attribute(v[(idx + 0) % 5]),
560 accessor.const_vector_attribute(v[(idx + 1) % 5]),
561 accessor.const_vector_attribute(v[(idx + 2) % 5]),
562 accessor.const_vector_attribute(v[(idx + 3) % 5]),
563 accessor.const_vector_attribute(v[(idx + 4) % 5]),
564 accessor.const_vector_attribute(e0),
565 accessor.const_vector_attribute(e1)}};
567 std::array<Eigen::Vector3d, 7> positions_double = {
576 std::array<std::array<int, 4>, 6> new_tets = {
584 double new_energy_max = std::numeric_limits<double>::lowest();
586 for (
int i = 0; i < 6; ++i) {
588 positions[new_tets[i][0]],
589 positions[new_tets[i][1]],
590 positions[new_tets[i][2]],
591 positions[new_tets[i][3]]) > 0) {
593 positions_double[new_tets[i][0]][0],
594 positions_double[new_tets[i][0]][1],
595 positions_double[new_tets[i][0]][2],
596 positions_double[new_tets[i][1]][0],
597 positions_double[new_tets[i][1]][1],
598 positions_double[new_tets[i][1]][2],
599 positions_double[new_tets[i][2]][0],
600 positions_double[new_tets[i][2]][1],
601 positions_double[new_tets[i][2]][2],
602 positions_double[new_tets[i][3]][0],
603 positions_double[new_tets[i][3]][1],
604 positions_double[new_tets[i][3]][2],
608 if (energy > new_energy_max) new_energy_max = energy;
611 positions_double[new_tets[i][1]][0],
612 positions_double[new_tets[i][1]][1],
613 positions_double[new_tets[i][1]][2],
614 positions_double[new_tets[i][0]][0],
615 positions_double[new_tets[i][0]][1],
616 positions_double[new_tets[i][0]][2],
617 positions_double[new_tets[i][2]][0],
618 positions_double[new_tets[i][2]][1],
619 positions_double[new_tets[i][2]][2],
620 positions_double[new_tets[i][3]][0],
621 positions_double[new_tets[i][3]][1],
622 positions_double[new_tets[i][3]][2],
626 if (energy > new_energy_max) new_energy_max = energy;
630 return new_energy_max;
633 swap56->set_value_function(swap56_energy_check);
634 swap56->add_invariant(std::make_shared<EdgeValenceInvariant>(position_mesh, 5));
638 auto swap44 = std::make_shared<MinOperationSequence>(position_mesh);
639 for (
int i = 0; i < 2; ++i) {
640 auto swap = std::make_shared<TetEdgeSwap>(position_mesh, i);
641 swap->collapse().add_invariant(invariant_separate_substructures);
643 swap->collapse().set_new_attribute_strategy(
645 CollapseBasicStrategy::CopyOther);
646 swap->collapse().set_new_attribute_strategy(
647 sizing_field_scalar_handle,
648 CollapseBasicStrategy::CopyOther);
649 swap->split().set_new_attribute_strategy(position_handle);
650 swap->split().set_new_attribute_strategy(sizing_field_scalar_handle);
651 swap->split().set_new_attribute_strategy(
652 visited_edge_flag_handle,
655 swap->collapse().set_new_attribute_strategy(
656 visited_edge_flag_handle,
659 swap->split().set_new_attribute_strategy(
660 target_edge_length_handle,
663 swap->collapse().set_new_attribute_strategy(
664 target_edge_length_handle,
667 swap->add_transfer_strategy(amips_update);
669 swap->add_invariant(std::make_shared<Swap44EnergyBeforeInvariantDouble>(
671 position_handle.as<
double>(),
675 swap->collapse().add_invariant(inversion_invariant);
677 swap->collapse().add_invariant(envelope_invariant);
679 for (
const auto& attr : pass_through_attributes) {
680 swap->split().set_new_attribute_strategy(
684 swap->collapse().set_new_attribute_strategy(
689 swap44->add_operation(swap);
692 auto swap44_energy_check = [&](int64_t idx,
const simplex::Simplex& t) ->
double {
702 const Tuple e0 = t.tuple();
705 std::array<Tuple, 4> v;
706 auto iter_tuple = e0;
707 for (int64_t i = 0; i < 4; ++i) {
712 if (iter_tuple != e0)
return 0;
713 assert(iter_tuple == e0);
715 std::array<Eigen::Vector3<double>, 6> positions = {
716 {accessor.const_vector_attribute(v[(idx + 0) % 4]),
717 accessor.const_vector_attribute(v[(idx + 1) % 4]),
718 accessor.const_vector_attribute(v[(idx + 2) % 4]),
719 accessor.const_vector_attribute(v[(idx + 3) % 4]),
720 accessor.const_vector_attribute(e0),
721 accessor.const_vector_attribute(e1)}};
722 std::array<Eigen::Vector3d, 6> positions_double = {
723 {positions[0], positions[1], positions[2], positions[3], positions[4], positions[5]}};
725 std::array<std::array<int, 4>, 4> new_tets = {
726 {{{0, 1, 2, 4}}, {{0, 2, 3, 4}}, {{0, 1, 2, 5}}, {{0, 2, 3, 5}}}};
728 double new_energy_max = std::numeric_limits<double>::lowest();
730 for (
int i = 0; i < 4; ++i) {
732 positions[new_tets[i][0]],
733 positions[new_tets[i][1]],
734 positions[new_tets[i][2]],
735 positions[new_tets[i][3]]) > 0) {
737 positions_double[new_tets[i][0]][0],
738 positions_double[new_tets[i][0]][1],
739 positions_double[new_tets[i][0]][2],
740 positions_double[new_tets[i][1]][0],
741 positions_double[new_tets[i][1]][1],
742 positions_double[new_tets[i][1]][2],
743 positions_double[new_tets[i][2]][0],
744 positions_double[new_tets[i][2]][1],
745 positions_double[new_tets[i][2]][2],
746 positions_double[new_tets[i][3]][0],
747 positions_double[new_tets[i][3]][1],
748 positions_double[new_tets[i][3]][2],
751 if (energy > new_energy_max) new_energy_max = energy;
754 positions_double[new_tets[i][1]][0],
755 positions_double[new_tets[i][1]][1],
756 positions_double[new_tets[i][1]][2],
757 positions_double[new_tets[i][0]][0],
758 positions_double[new_tets[i][0]][1],
759 positions_double[new_tets[i][0]][2],
760 positions_double[new_tets[i][2]][0],
761 positions_double[new_tets[i][2]][1],
762 positions_double[new_tets[i][2]][2],
763 positions_double[new_tets[i][3]][0],
764 positions_double[new_tets[i][3]][1],
765 positions_double[new_tets[i][3]][2],
768 if (energy > new_energy_max) new_energy_max = energy;
772 return new_energy_max;
775 swap44->set_value_function(swap44_energy_check);
776 swap44->add_invariant(std::make_shared<EdgeValenceInvariant>(position_mesh, 4));
779 auto swap32 = std::make_shared<TetEdgeSwap>(position_mesh, 0);
780 swap32->add_invariant(std::make_shared<EdgeValenceInvariant>(position_mesh, 3));
781 swap32->add_invariant(std::make_shared<Swap32EnergyBeforeInvariantDouble>(
783 position_handle.as<
double>()));
785 swap32->collapse().add_invariant(invariant_separate_substructures);
787 swap32->collapse().set_new_attribute_strategy(
789 CollapseBasicStrategy::CopyOther);
790 swap32->collapse().set_new_attribute_strategy(
791 sizing_field_scalar_handle,
792 CollapseBasicStrategy::CopyOther);
794 swap32->split().set_new_attribute_strategy(position_handle);
795 swap32->split().set_new_attribute_strategy(sizing_field_scalar_handle);
796 swap32->split().set_new_attribute_strategy(
797 visited_edge_flag_handle,
800 swap32->collapse().set_new_attribute_strategy(
801 visited_edge_flag_handle,
804 swap32->split().set_new_attribute_strategy(
805 target_edge_length_handle,
808 swap32->collapse().set_new_attribute_strategy(
809 target_edge_length_handle,
812 swap32->add_transfer_strategy(amips_update);
815 swap32->collapse().add_invariant(inversion_invariant);
816 swap32->collapse().add_invariant(envelope_invariant);
818 for (
const auto& attr : pass_through_attributes) {
819 swap32->split().set_new_attribute_strategy(
823 swap32->collapse().set_new_attribute_strategy(
828 auto swap_all = std::make_shared<OrOperationSequence>(position_mesh);
829 swap_all->add_operation(swap32);
830 swap_all->add_operation(swap44);
831 swap_all->add_operation(swap56);
832 swap_all->add_transfer_strategy(tag_update);
833 swap_all->add_transfer_strategy(energy_filter_update);
834 swap_all->add_invariant(
835 std::make_shared<EnergyFilterInvariant>(position_mesh, energy_filter_handle.as<
char>()));
836 swap_all->add_invariant(std::make_shared<InteriorEdgeInvariant>(position_mesh));
837 swap_all->add_invariant(std::make_shared<NoChildMeshAttachingInvariant>(position_mesh));
839 swap_all->set_priority(long_edges_first);
841 swap_all->add_transfer_strategy(update_child_position);
843 ops.push_back(swap_all);
844 ops_name.push_back(
"EDGE SWAP");
851 std::vector<MeshConstrainPair> mesh_constraint_pairs;
852 mesh_constraint_pairs.emplace_back(surface_position_handle, surface_position_handle);
854 auto smoothing = std::make_shared<AMIPSOptimizationSmoothingPeriodic>(
858 smoothing->add_invariant(inversion_invariant);
859 smoothing->add_transfer_strategy(update_child_position);
860 smoothing->use_random_priority() =
true;
862 smoothing->add_invariant(envelope_invariant);
863 smoothing->add_invariant(
864 std::make_shared<EnergyFilterInvariant>(position_mesh, energy_filter_handle.as<
char>()));
866 smoothing->add_transfer_strategy(amips_update);
867 smoothing->add_transfer_strategy(edge_length_update);
868 smoothing->add_transfer_strategy(update_child_position);
870 ops.push_back(smoothing);
871 ops_name.push_back(
"SMOOTHING");
950 write(position_mesh, output_dir, output +
"_initial",
"vertices", 0, intermediate_output);
956 double old_max_energy = max_amips;
957 double old_avg_energy = 0;
959 for (int64_t i = 0; i < passes; ++i) {
960 logger().info(
"--------------------------- Pass {} ---------------------------", i);
965 for (
auto& op : ops) {
968 std::vector<Vector3d> pos;
969 for (
int i = 0; i <
vertices.size(); ++i) {
970 pos.push_back(position_accessor.const_vector_attribute(
vertices[i]));
976 logger().info(
"Executing {} ...", ops_name[jj]);
985 "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, "
1064 double avg_energy = 0;
1065 double max_energy = std::numeric_limits<double>::lowest();
1066 double min_energy = std::numeric_limits<double>::max();
1068 double e = amips_accessor.scalar_attribute(t);
1069 max_energy = std::max(max_energy, e);
1070 min_energy = std::min(min_energy, e);
1078 "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}",
1087 "Executed {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, executing: {}",
1097 write(position_mesh, output_dir, output,
"vertices", i + 1, intermediate_output);
1099 double max_energy = std::numeric_limits<double>::lowest();
1100 double min_energy = std::numeric_limits<double>::max();
1101 double avg_energy = 0;
1103 double e = amips_accessor.scalar_attribute(t);
1104 max_energy = std::max(max_energy, e);
1105 min_energy = std::min(min_energy, e);
1112 "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}",
1117 if (i > 0 && old_max_energy - max_energy < 5e-1 &&
1118 (old_avg_energy - avg_energy) / avg_energy < 0.1) {
1123 position_handle.as<
double>(),
1124 edge_length_handle.as<
double>(),
1125 sizing_field_scalar_handle.as<
double>(),
1126 amips_handle.as<
double>(),
1127 target_edge_length_handle.as<
double>(),
1128 visited_vertex_flag_handle.as<
char>(),
1134 wmtk::logger().info(
"adjusting sizing field finished");
1136 energy_filter_accessor.scalar_attribute(v) = char(1);
1153 old_max_energy = max_energy;
1154 old_avg_energy = avg_energy;