diff --git a/bindings/python/requirements.txt b/bindings/python/requirements.txt index a992b42b..79d955ec 100644 --- a/bindings/python/requirements.txt +++ b/bindings/python/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile bindings/python/requirements.in +# pip-compile --pre bindings/python/requirements.in # opengeode-core==14.*,>=14.17.0 # via -r bindings/python/requirements.in diff --git a/bindings/python/src/implicit/representation/core/helpers.h b/bindings/python/src/implicit/representation/core/helpers.h index fe7db92e..f4a2a397 100644 --- a/bindings/python/src/implicit/representation/core/helpers.h +++ b/bindings/python/src/implicit/representation/core/helpers.h @@ -50,6 +50,14 @@ namespace geode .def( "save_stratigraphic_surfaces", &save_stratigraphic_surfaces ) .def( "save_stratigraphic_blocks", &save_stratigraphic_blocks ) + .def( "horizons_stack_from_name_list_2d", + &horizons_stack_from_name_list< 2 > ) + .def( "horizons_stack_from_name_list_3d", + &horizons_stack_from_name_list< 3 > ) + .def( "repair_horizon_stack_if_possible_2d", + &repair_horizon_stack_if_possible< 2 > ) + .def( "repair_horizon_stack_if_possible_3d", + &repair_horizon_stack_if_possible< 3 > ) .def( "implicit_section_from_cross_section_scalar_field", []( CrossSection& model, absl::string_view attribute_name ) { diff --git a/include/geode/geosciences/implicit/representation/core/detail/helpers.h b/include/geode/geosciences/implicit/representation/core/detail/helpers.h index 18a019da..05320f15 100644 --- a/include/geode/geosciences/implicit/representation/core/detail/helpers.h +++ b/include/geode/geosciences/implicit/representation/core/detail/helpers.h @@ -23,10 +23,14 @@ #pragma once +#include + #include namespace geode { + FORWARD_DECLARATION_DIMENSION_CLASS( HorizonsStack ); + ALIAS_2D_AND_3D( HorizonsStack ); class CrossSection; class StructuralModel; class ImplicitCrossSection; @@ -74,6 +78,19 @@ namespace geode ImplicitStructuralModel&& implicit_model, local_index_t implicit_axis ); + /*! + * Creates a HorizonsStack from a list of names of Horizons and + * StratigraphicUnits. + */ + template < index_t dimension > + HorizonsStack< dimension > horizons_stack_from_name_list( + absl::Span< const std::string > horizons_names, + absl::Span< const std::string > units_names ); + + template < index_t dimension > + void repair_horizon_stack_if_possible( + HorizonsStack< dimension >& horizon_stack ); + std::vector< MeshElement > opengeode_geosciences_implicit_api invalid_stratigraphic_tetrahedra( const StratigraphicModel& model ); } // namespace detail diff --git a/src/geode/geosciences/implicit/representation/core/detail/helpers.cpp b/src/geode/geosciences/implicit/representation/core/detail/helpers.cpp index daf903a4..7b44bdd4 100644 --- a/src/geode/geosciences/implicit/representation/core/detail/helpers.cpp +++ b/src/geode/geosciences/implicit/representation/core/detail/helpers.cpp @@ -42,14 +42,32 @@ #include #include +#include #include #include #include +#include #include #include #include #include +namespace +{ + void check_number_of_horizons_and_stratigraphic_units( + geode::index_t nb_horizons, geode::index_t nb_units ) + { + OPENGEODE_EXCEPTION( nb_horizons < nb_units + 2, + "[repair_horizon_stack_if_possible] Too many horizons compared " + "to stratigraphic units (", + nb_horizons, ", should be less than ", nb_units, ")" ); + OPENGEODE_EXCEPTION( nb_units < nb_horizons + 2, + "[repair_horizon_stack_if_possible] Too many stratigraphic " + "units compared to horizons (", + nb_units, ", should be less than ", nb_horizons, ")" ); + } +} // namespace + namespace geode { namespace detail @@ -259,6 +277,97 @@ namespace geode return { std::move( implicit_model ) }; } + template < index_t dimension > + HorizonsStack< dimension > horizons_stack_from_name_list( + absl::Span< const std::string > horizons_names, + absl::Span< const std::string > units_names ) + { + OPENGEODE_EXCEPTION( !horizons_names.empty(), + "[horizons_stack_from_name_list] Cannot create HorizonsStack: " + "horizons_names list is empty." ); + const auto nb_horizons = horizons_names.size(); + const auto nb_units = units_names.size(); + check_number_of_horizons_and_stratigraphic_units( + nb_horizons, nb_units ); + HorizonsStack< dimension > stack; + HorizonsStackBuilder< dimension > builder{ stack }; + auto current_horizon = builder.add_horizon(); + builder.set_horizon_name( current_horizon, horizons_names[0] ); + const auto& su_under = builder.add_stratigraphic_unit(); + builder.add_horizon_above( stack.horizon( current_horizon ), + stack.stratigraphic_unit( su_under ) ); + bool lowest_unit_to_create{ nb_units < nb_horizons }; + if( !lowest_unit_to_create ) + { + builder.set_stratigraphic_unit_name( su_under, units_names[0] ); + } + for( const auto counter : Range{ 1, horizons_names.size() } ) + { + const auto& su_above = builder.add_stratigraphic_unit(); + builder.set_stratigraphic_unit_name( su_above, + units_names[counter + lowest_unit_to_create ? -1 : 0] ); + builder.add_horizon_under( stack.horizon( current_horizon ), + stack.stratigraphic_unit( su_above ) ); + current_horizon = builder.add_horizon(); + builder.set_horizon_name( + current_horizon, horizons_names[counter] ); + builder.add_horizon_above( stack.horizon( current_horizon ), + stack.stratigraphic_unit( su_above ) ); + } + const auto& su_above = builder.add_stratigraphic_unit(); + builder.add_horizon_under( stack.horizon( current_horizon ), + stack.stratigraphic_unit( su_above ) ); + if( nb_units > nb_horizons ) + { + builder.set_stratigraphic_unit_name( + su_above, units_names.back() ); + } + return stack; + } + + template < index_t dimension > + void repair_horizon_stack_if_possible( + HorizonsStack< dimension >& horizon_stack ) + { + const auto nb_horizons = horizon_stack.nb_horizons(); + check_number_of_horizons_and_stratigraphic_units( + nb_horizons, horizon_stack.nb_stratigraphic_units() ); + const auto bottom_horizon = horizon_stack.bottom_horizon(); + if( !horizon_stack.under( bottom_horizon ) ) + { + HorizonsStackBuilder< dimension > builder{ horizon_stack }; + const auto& unit_under = builder.add_stratigraphic_unit(); + builder.add_horizon_above( + horizon_stack.horizon( bottom_horizon ), + horizon_stack.stratigraphic_unit( unit_under ) ); + } + index_t horizon_counter{ 1 }; + auto su_above = horizon_stack.above( bottom_horizon ); + absl::optional< uuid > current_horizon = bottom_horizon; + while( su_above ) + { + current_horizon = horizon_stack.above( su_above.value() ); + if( !current_horizon ) + { + break; + } + su_above = horizon_stack.above( current_horizon.value() ); + horizon_counter++; + } + OPENGEODE_EXCEPTION( horizon_counter == nb_horizons, + "[repair_horizon_stack_if_possible] Missing or wrong " + "above/under relations between horizons and stratigraphic " + "units." ); + if( !su_above ) + { + HorizonsStackBuilder< dimension > builder{ horizon_stack }; + const auto& unit_above = builder.add_stratigraphic_unit(); + builder.add_horizon_under( + horizon_stack.horizon( current_horizon.value() ), + horizon_stack.stratigraphic_unit( unit_above ) ); + } + } + std::vector< MeshElement > invalid_stratigraphic_tetrahedra( const StratigraphicModel& implicit_model ) { @@ -301,5 +410,21 @@ namespace geode } return invalid_tetrahedra; } + + template HorizonsStack< 2 > opengeode_geosciences_implicit_api + horizons_stack_from_name_list< 2 >( + absl::Span< const std::string > horizons_names, + absl::Span< const std::string > units_names ); + template HorizonsStack< 3 > opengeode_geosciences_implicit_api + horizons_stack_from_name_list< 3 >( + absl::Span< const std::string > horizons_names, + absl::Span< const std::string > units_names ); + + template void opengeode_geosciences_implicit_api + repair_horizon_stack_if_possible< 2 >( + HorizonsStack< 2 >& horizon_stack ); + template void opengeode_geosciences_implicit_api + repair_horizon_stack_if_possible< 3 >( + HorizonsStack< 3 >& horizon_stack ); } // namespace detail } // namespace geode \ No newline at end of file diff --git a/tests/implicit/test-horizons-stack.cpp b/tests/implicit/test-horizons-stack.cpp index c92daa51..e8b1f7ad 100644 --- a/tests/implicit/test-horizons-stack.cpp +++ b/tests/implicit/test-horizons-stack.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -44,9 +45,9 @@ void test_horizons_stack() const geode::uuid unit1{}; stack_builder.add_stratigraphic_unit( unit1 ); OPENGEODE_EXCEPTION( horizons_stack.nb_horizons() == 3, - "[Test] Stratigraphic Units Stack should have 3 horizons." ); + "[Test] Horizons Stack should have 3 horizons." ); OPENGEODE_EXCEPTION( horizons_stack.nb_stratigraphic_units() == 2, - "[Test] Stratigraphic Units Stack should have 2 Stratigraphic Units." ); + "[Test] Horizons Stack should have 2 Stratigraphic Units." ); stack_builder.add_horizon_under( horizons_stack.horizon( hor0 ), horizons_stack.stratigraphic_unit( unit0 ) ); stack_builder.add_horizon_above( horizons_stack.horizon( hor1 ), @@ -78,12 +79,51 @@ void test_horizons_stack() == insertion_info.new_horizon_id, "[Test] New horizon should be found from horizon 1." ); + geode::detail::repair_horizon_stack_if_possible( horizons_stack ); + OPENGEODE_EXCEPTION( horizons_stack.nb_horizons() == 4, + "[Test] Horizons Stack should have 4 horizons after repair." ); + OPENGEODE_EXCEPTION( horizons_stack.nb_stratigraphic_units() == 5, + "[Test] Horizons Stack should have 5 Stratigraphic Units after " + "repair." ); + for( const auto& horizon : horizons_stack.horizons() ) + { + const auto& horizon_id = horizon.id(); + OPENGEODE_EXCEPTION( horizons_stack.above( horizon_id ), + "[Test] Horizon should have a unit above." ); + OPENGEODE_EXCEPTION( horizons_stack.under( horizon_id ), + "[Test] Horizon should have a unit under." ); + } + const auto stack_path = absl::StrCat( "test_HorizonStack.", geode::HorizonsStack3D::native_extension_static() ); geode::save_horizons_stack( horizons_stack, stack_path ); auto reloaded_stack = geode::load_horizons_stack< 3 >( stack_path ); } +void test_create_horizons_stack() +{ + std::array< std::string, 4 > horizons_list{ "h1", "h2", "h3", "h4" }; + std::array< std::string, 3 > units_list{ "su1", "su2", "su3" }; + const auto horizons_stack = + geode::detail::horizons_stack_from_name_list< 2 >( + horizons_list, units_list ); + OPENGEODE_EXCEPTION( horizons_stack.nb_horizons() == 3, + "[Test] Created Horizons Stack should have 4 horizons." ); + OPENGEODE_EXCEPTION( horizons_stack.nb_stratigraphic_units() == 5, + "[Test] Created Horizons Stack should have 5 Stratigraphic Units." ); + const auto bot_horizon = horizons_stack.bottom_horizon(); + OPENGEODE_EXCEPTION( + horizons_stack.horizon( bot_horizon ).name() == horizons_list[0], + "[Test] Wrong name for bottom horizon." ); + OPENGEODE_EXCEPTION( + horizons_stack + .stratigraphic_unit( + horizons_stack.above( bot_horizon ).value() ) + .name() + == units_list[0], + "[Test] Wrong name for unit above bottom horizon." ); +} + int main() { try